FileDocCategorySizeDatePackage
MP3AudioHeader.javaAPI DocJaudiotagger 2.0.427538Mon Sep 12 16:41:46 BST 2011org.jaudiotagger.audio.mp3

MP3AudioHeader

public class MP3AudioHeader extends Object implements org.jaudiotagger.audio.AudioHeader
Represents the audio header of an MP3 File

The audio header consists of a number of audio frames. Because we are not trying to play the audio but only extract some information regarding the audio we only need to read the first audio frames to ensure that we have correctly identified them as audio frames and extracted the metadata we reuire.

Start of Audio id 0xFF (11111111) and then second byte anded with 0xE0(11100000). For example 2nd byte doesnt have to be 0xE0 is just has to have the top 3 signicant bits set. For example 0xFB (11111011) is a common occurence of the second match. The 2nd byte defines flags to indicate various mp3 values.

Having found these two values we then read the header which comprises these two bytes plus a further two to ensure this really is a MP3Header, sometimes the first frame is actually a dummy frame with summary information held within about the whole file, typically using a Xing Header or LAme Header. This is most useful when the file is variable bit rate, if the file is variable bit rate but does not use a summary header it will not be correctly identified as a VBR frame and the track length will be incorrectly calculated. Strictly speaking MP3 means Layer III file but MP2 Layer II), MP1 Layer I) and MPEG-2 files are sometimes used and named with the .mp3 suffix so this library attempts to supports all these formats.

Fields Summary
protected MPEGFrameHeader
mp3FrameHeader
protected XingFrame
mp3XingFrame
protected VbriFrame
mp3VbriFrame
private long
fileSize
private long
startByte
private double
timePerFrame
private double
trackLength
private long
numberOfFrames
private long
numberOfFramesEstimate
private long
bitrate
private String
encoder
private static final SimpleDateFormat
timeInFormat
private static final SimpleDateFormat
timeOutFormat
private static final SimpleDateFormat
timeOutOverAnHourFormat
private static final char
isVbrIdentifier
private static final int
CONVERT_TO_KILOBITS
private static final String
TYPE_MP3
private static final int
CONVERTS_BYTE_TO_BITS
public static Logger
logger
private static final int
FILE_BUFFER_SIZE
After testing the average location of the first MP3Header bit was at 5000 bytes so this is why chosen as a default.
private static final int
MIN_BUFFER_REMAINING_REQUIRED
private static final int
NO_SECONDS_IN_HOUR
Constructors Summary
public MP3AudioHeader()


     
    
    
public MP3AudioHeader(File seekFile)
Search for the first MP3Header in the file

The search starts from the start of the file, it is usually safer to use the alternative constructor that allows you to provide the length of the tag header as a parameter so the tag can be skipped over.

param
seekFile
throws
IOException
throws
InvalidAudioFrameException

        if (!seek(seekFile, 0))
        {
            throw new InvalidAudioFrameException("No audio header found within" + seekFile.getName());
        }
    
public MP3AudioHeader(File seekFile, long startByte)
Search for the first MP3Header in the file

Starts searching from location startByte, this is because there is likely to be an ID3TagHeader before the start of the audio. If this tagHeader contains unsynchronized information there is a possibility that it might be inaccurately identified as the start of the Audio data. Various checks are done in this code to prevent this happening but it cannot be guaranteed.

Of course if the startByte provided overstates the length of the tag header, this could mean the start of the MP3AudioHeader is missed, further checks are done within the MP3 class to recognize if this has occurred and take appropriate action.

param
seekFile
param
startByte
throws
IOException
throws
InvalidAudioFrameException

        if (!seek(seekFile, startByte))
        {
            throw new InvalidAudioFrameException("No audio header found within" + seekFile.getName());
        }
    
Methods Summary
public java.lang.StringgetBitRate()

return
the BitRate of the Audio, to distinguish cbr from vbr we add a '~' for vbr.

        if (mp3XingFrame != null && mp3XingFrame.isVbr())
        {
            return isVbrIdentifier + String.valueOf(bitrate);
        }
        else if (mp3VbriFrame != null)
        {
            return isVbrIdentifier + String.valueOf(bitrate);
        }
        else
        {
            return String.valueOf(bitrate);
        }
    
public longgetBitRateAsNumber()

return
bitrate in kbps, no indicator is provided as to whether or not it is vbr

        return bitrate;
    
public java.lang.StringgetChannels()

return
the Channel Mode such as Stero or Mono

        return mp3FrameHeader.getChannelModeAsString();
    
public java.lang.StringgetEmphasis()

return
Emphasis

        return mp3FrameHeader.getEmphasisAsString();
    
public java.lang.StringgetEncoder()

return
encoder

        return encoder;
    
public java.lang.StringgetEncodingType()

return
the audio file type

        return TYPE_MP3;
    
public java.lang.StringgetFormat()

return
the format of the audio (i.e. MPEG-1 Layer3)

        return mp3FrameHeader.getVersionAsString() + " " + mp3FrameHeader.getLayerAsString();
    
public longgetMp3StartByte()
Returns the byte position of the first MP3 Frame that the file arguement refers to. This is the first byte of music data and not the ID3 Tag Frame.

return
the byte position of the first MP3 Frame

        return startByte;
    
public java.lang.StringgetMpegLayer()

return
MPEG Layer (1-3)

        return mp3FrameHeader.getLayerAsString();
    
public java.lang.StringgetMpegVersion()

return
MPEG Version (1-3)

        return mp3FrameHeader.getVersionAsString();
    
public longgetNumberOfFrames()

return
The number of frames within the Audio File, calculated as accurrately as possible

        return numberOfFrames;
    
public longgetNumberOfFramesEstimate()

return
The number of frames within the Audio File, calculated by dividing the filesize by the number of frames, this may not be the most accurate method available.

        return numberOfFramesEstimate;
    
public doublegetPreciseTrackLength()

return
Track Length in seconds

        return trackLength;
    
public java.lang.StringgetSampleRate()

return
the sampling rate as string

        return String.valueOf(mp3FrameHeader.getSamplingRate());
    
public intgetSampleRateAsNumber()

return
the sampling rate in Hz

        return mp3FrameHeader.getSamplingRate();
    
private doublegetTimePerFrame()

return
the the time each frame contributes to the audio in fractions of seconds

        return timePerFrame;
    
public intgetTrackLength()

        return (int) getPreciseTrackLength();
    
public java.lang.StringgetTrackLengthAsString()
Return the length in user friendly format

return

        final Date timeIn;
        try
        {
            final long lengthInSecs = getTrackLength();
            synchronized(timeInFormat)
            {
                timeIn = timeInFormat.parse(String.valueOf(lengthInSecs));
            }

            if (lengthInSecs < NO_SECONDS_IN_HOUR)
            {
                synchronized(timeOutFormat)
                {
                    return timeOutFormat.format(timeIn);
                }
            }
            else
            {
                synchronized(timeOutOverAnHourFormat)
                {
                    return timeOutOverAnHourFormat.format(timeIn);
                }
            }
        }
        catch (ParseException pe)
        {
            logger.warning("Unable to parse:"+getPreciseTrackLength() +" failed with ParseException:"+pe.getMessage());
            return "";
        }
    
public booleanisCopyrighted()

        return mp3FrameHeader.isCopyrighted();
    
private booleanisNextFrameValid(java.io.File seekFile, long filePointerCount, java.nio.ByteBuffer bb, java.nio.channels.FileChannel fc)
Called in some circumstances to check the next frame to ensure we have the correct audio header

param
seekFile
param
filePointerCount
param
bb
param
fc
return
true if frame is valid
throws
java.io.IOException

        if (MP3AudioHeader.logger.isLoggable(Level.FINEST))
        {
            MP3AudioHeader.logger.finer("Checking next frame" + seekFile.getName() + ":fpc:" + filePointerCount + "skipping to:" + (filePointerCount + mp3FrameHeader.getFrameLength()));
        }
        boolean result = false;

        int currentPosition = bb.position();

        //Our buffer is not large enough to fit in the whole of this frame, something must
        //have gone wrong because frames are not this large, so just return false
        //bad frame header
        if (mp3FrameHeader.getFrameLength() > (FILE_BUFFER_SIZE - MIN_BUFFER_REMAINING_REQUIRED))
        {
            MP3AudioHeader.logger.finer("Frame size is too large to be a frame:" + mp3FrameHeader.getFrameLength());
            return false;
        }

        //Check for end of buffer if not enough room get some more
        if (bb.remaining() <= MIN_BUFFER_REMAINING_REQUIRED + mp3FrameHeader.getFrameLength())
        {
            MP3AudioHeader.logger.finer("Buffer too small, need to reload, buffer size:" + bb.remaining());
            bb.clear();
            fc.position(filePointerCount);
            fc.read(bb, fc.position());
            bb.flip();
            //So now original buffer has been replaced, so set current position to start of buffer
            currentPosition = 0;
            //Not enough left
            if (bb.limit() <= MIN_BUFFER_REMAINING_REQUIRED)
            {
                //No mp3 exists
                MP3AudioHeader.logger.finer("Nearly at end of file, no header found:");
                return false;
            }

            //Still Not enough left for next alleged frame size so giving up
            if (bb.limit() <= MIN_BUFFER_REMAINING_REQUIRED + mp3FrameHeader.getFrameLength())
            {
                //No mp3 exists
                MP3AudioHeader.logger.finer("Nearly at end of file, no room for next frame, no header found:");
                return false;
            }
        }

        //Position bb to the start of the alleged next frame
        bb.position(bb.position() + mp3FrameHeader.getFrameLength());
        if (MPEGFrameHeader.isMPEGFrame(bb))
        {
            try
            {
                MPEGFrameHeader.parseMPEGHeader(bb);
                MP3AudioHeader.logger.finer("Check next frame confirms is an audio header ");
                result = true;
            }
            catch (InvalidAudioFrameException ex)
            {
                MP3AudioHeader.logger.finer("Check next frame has identified this is not an audio header");
                result = false;
            }
        }
        else
        {
            MP3AudioHeader.logger.finer("isMPEGFrame has identified this is not an audio header");
        }
        //Set back to the start of the previous frame
        bb.position(currentPosition);
        return result;
    
public booleanisOriginal()

        return mp3FrameHeader.isOriginal();
    
public booleanisPadding()

        return mp3FrameHeader.isPadding();
    
public booleanisPrivate()

        return mp3FrameHeader.isPrivate();
    
public booleanisProtected()

        return mp3FrameHeader.isProtected();
    
public booleanisVariableBitRate()

return
if the bitrate is variable, Xing header takes precedence if we have one

        if (mp3XingFrame != null)
        {
            return mp3XingFrame.isVbr();
        }
        else if (mp3VbriFrame != null)
        {
            return mp3VbriFrame.isVbr();
        }
        else
        {
            return mp3FrameHeader.isVariableBitRate();
        }
    
public booleanseek(java.io.File seekFile, long startByte)
Returns true if the first MP3 frame can be found for the MP3 file

This is the first byte of music data and not the ID3 Tag Frame. *

param
seekFile MP3 file to seek
param
startByte if there is an ID3v2tag we dont want to start reading from the start of the tag
return
true if the first MP3 frame can be found
throws
IOException on any I/O error
noinspection
NestedTryStatement

        //References to Xing/VRbi Header
        ByteBuffer header;

        //This is substantially faster than updating the filechannels position
        long filePointerCount;

        final FileInputStream fis = new FileInputStream(seekFile);
        final FileChannel fc = fis.getChannel();

        //Read into Byte Buffer in Chunks
        ByteBuffer bb = ByteBuffer.allocateDirect(FILE_BUFFER_SIZE);

        //Move FileChannel to the starting position (skipping over tag if any)
        fc.position(startByte);

        //Update filePointerCount
        filePointerCount = startByte;

        //Read from here into the byte buffer , doesn't move location of filepointer
        fc.read(bb, startByte);
        bb.flip();

        boolean syncFound = false;
        try
        {
            do
            {
                //TODO remaining() is quite an expensive operation, isn't there a way we can work this out without
                //interrogating the bytebuffer. Also this is rarely going to be true, and could be made less true
                //by increasing FILE_BUFFER_SIZE
                if (bb.remaining() <= MIN_BUFFER_REMAINING_REQUIRED)
                {
                    bb.clear();
                    fc.position(filePointerCount);
                    fc.read(bb, fc.position());
                    bb.flip();
                    if (bb.limit() <= MIN_BUFFER_REMAINING_REQUIRED)
                    {
                        //No mp3 exists
                        return false;
                    }
                }
                //MP3File.logger.finest("fc:"+fc.position() + "bb"+bb.position());
                if (MPEGFrameHeader.isMPEGFrame(bb))
                {
                    try
                    {
                        if (MP3AudioHeader.logger.isLoggable(Level.FINEST))
                        {
                            MP3AudioHeader.logger.finest("Found Possible header at:" + filePointerCount);
                        }

                        mp3FrameHeader = MPEGFrameHeader.parseMPEGHeader(bb);
                        syncFound = true;
                        //if(2==1) use this line when you want to test getting the next frame without using xing

                        if ((header = XingFrame.isXingFrame(bb, mp3FrameHeader))!=null)
                        {
                            if (MP3AudioHeader.logger.isLoggable(Level.FINEST))
                            {
                                MP3AudioHeader.logger.finest("Found Possible XingHeader");
                            }
                            try
                            {
                                //Parses Xing frame without modifying position of main buffer
                                mp3XingFrame = XingFrame.parseXingFrame(header);
                            }
                            catch (InvalidAudioFrameException ex)
                            {
                                // We Ignore because even if Xing Header is corrupted
                                //doesn't mean file is corrupted
                            }
                            break;
                        }
                        else if ((header = VbriFrame.isVbriFrame(bb, mp3FrameHeader))!=null)
                        {
                            if (MP3AudioHeader.logger.isLoggable(Level.FINEST))
                            {
                                MP3AudioHeader.logger.finest("Found Possible VbriHeader");
                            }
                            try
                            {
                                //Parses Vbri frame without modifying position of main buffer
                                mp3VbriFrame = VbriFrame.parseVBRIFrame(header);
                            }
                            catch (InvalidAudioFrameException ex)
                            {
                                // We Ignore because even if Vbri Header is corrupted
                                //doesn't mean file is corrupted
                            }
                            break;
                        }
                        // There is a small but real chance that an unsynchronised ID3 Frame could fool the MPEG
                        // Parser into thinking it was an MPEG Header. If this happens the chances of the next bytes
                        // forming a Xing frame header are very remote. On the basis that  most files these days have
                        // Xing headers we do an additional check for when an apparent frame header has been found
                        // but is not followed by a Xing Header:We check the next header this wont impose a large
                        // overhead because wont apply to most Mpegs anyway ( Most likely to occur if audio
                        // has an  APIC frame which should have been unsynchronised but has not been) , or if the frame
                        // has been encoded with as Unicode LE because these have a BOM of 0xFF 0xFE
                        else
                        {
                            syncFound = isNextFrameValid(seekFile, filePointerCount, bb, fc);
                            if (syncFound)
                            {
                                break;
                            }
                        }

                    }
                    catch (InvalidAudioFrameException ex)
                    {
                        // We Ignore because likely to be incorrect sync bits ,
                        // will just continue in loop
                    }
                }

                //TODO position() is quite an expensive operation, isn't there a way we can work this out without
                //interrogating the bytebuffer
                bb.position(bb.position() + 1);
                filePointerCount++;


            }
            while (!syncFound);
        }
        catch (EOFException ex)
        {
            MP3AudioHeader.logger.log(Level.WARNING, "Reached end of file without finding sync match", ex);
            syncFound = false;
        }
        catch (IOException iox)
        {
            MP3AudioHeader.logger.log(Level.SEVERE, "IOException occurred whilst trying to find sync", iox);
            syncFound = false;
            throw iox;
        }
        finally
        {
            if (fc != null)
            {
                fc.close();
            }

            if (fis != null)
            {
                fis.close();
            }
        }

        //Return to start of audio header
        if (MP3AudioHeader.logger.isLoggable(Level.FINEST))
        {
            MP3AudioHeader.logger.finer("Return found matching mp3 header starting at" + filePointerCount);
        }
        setFileSize(seekFile.length());
        setMp3StartByte(filePointerCount);
        setTimePerFrame();
        setNumberOfFrames();
        setTrackLength();
        setBitRate();
        setEncoder();
        /*if((filePointerCount - startByte )>0)
        {
            logger.severe(seekFile.getName()+"length:"+startByte+"Difference:"+(filePointerCount - startByte));
        }
        */
        return syncFound;
    
protected voidsetBitRate()
Set bitrate in kbps, if Vbr use Xingheader if possible


        if (mp3XingFrame != null && mp3XingFrame.isVbr())
        {
            if (mp3XingFrame.isAudioSizeEnabled() && mp3XingFrame.getAudioSize() > 0)
            {
                bitrate = (long) ((mp3XingFrame.getAudioSize() * CONVERTS_BYTE_TO_BITS) / (timePerFrame * getNumberOfFrames() * CONVERT_TO_KILOBITS));
            }
            else
            {
                bitrate = (long) (((fileSize - startByte) * CONVERTS_BYTE_TO_BITS) / (timePerFrame * getNumberOfFrames() * CONVERT_TO_KILOBITS));
            }
        }
        else if (mp3VbriFrame != null)
        {
            if (mp3VbriFrame.getAudioSize() > 0)
            {
                bitrate = (long) ((mp3VbriFrame.getAudioSize() * CONVERTS_BYTE_TO_BITS) / (timePerFrame * getNumberOfFrames() * CONVERT_TO_KILOBITS));
            }
            else
            {
                bitrate = (long) (((fileSize - startByte) * CONVERTS_BYTE_TO_BITS) / (timePerFrame * getNumberOfFrames() * CONVERT_TO_KILOBITS));
            }
        }
        else
        {
            bitrate = mp3FrameHeader.getBitRate();
        }
    
protected voidsetEncoder()

        if (mp3XingFrame != null)
        {
            if (mp3XingFrame.getLameFrame() != null)
            {
                encoder = mp3XingFrame.getLameFrame().getEncoder();
            }
        }
        else if (mp3VbriFrame != null)
        {
            encoder = mp3VbriFrame.getEncoder();
        }
    
protected voidsetFileSize(long fileSize)
Set the size of the file, required in some calculations

param
fileSize

        this.fileSize = fileSize;
    
protected voidsetMp3StartByte(long startByte)
Set the location of where the Audio file begins in the file

param
startByte

        this.startByte = startByte;
    
protected voidsetNumberOfFrames()
Set number of frames in this file, use Xing if exists otherwise ((File Size - Non Audio Part)/Frame Size)

        numberOfFramesEstimate = (fileSize - startByte) / mp3FrameHeader.getFrameLength();

        if (mp3XingFrame != null && mp3XingFrame.isFrameCountEnabled())
        {
            numberOfFrames = mp3XingFrame.getFrameCount();
        }
        else if (mp3VbriFrame != null)
        {
            numberOfFrames = mp3VbriFrame.getFrameCount();
        }
        else
        {
            numberOfFrames = numberOfFramesEstimate;
        }

    
protected voidsetTimePerFrame()
Set the time each frame contributes to the audio in fractions of seconds, the higher the sampling rate the shorter the audio segment provided by the frame, the number of samples is fixed by the MPEG Version and Layer

        timePerFrame = mp3FrameHeader.getNoOfSamples() / mp3FrameHeader.getSamplingRate().doubleValue();
    
protected voidsetTrackLength()
Estimate the length of the audio track in seconds Calculation is Number of frames multiplied by the Time Per Frame using the first frame as a prototype Time Per Frame is the number of samples in the frame (which is defined by the MPEGVersion/Layer combination) divided by the sampling rate, i.e the higher the sampling rate the shorter the audio represented by the frame is going to be.

        trackLength = numberOfFrames * getTimePerFrame();
    
public java.lang.StringtoString()

return
a string representation

        String s = "fileSize:" + fileSize + " encoder:" + encoder + " startByte:" + Hex.asHex(startByte) + " numberOfFrames:" + numberOfFrames + " numberOfFramesEst:" + numberOfFramesEstimate + " timePerFrame:" + timePerFrame + " bitrate:" + bitrate + " trackLength:" + getTrackLengthAsString();

        if (this.mp3FrameHeader != null)
        {
            s += mp3FrameHeader.toString();
        }

        if (this.mp3XingFrame != null)
        {
            s += mp3XingFrame.toString();
        }
        return s;