Coverage Report - org.jaudiotagger.audio.mp3.XingFrame
 
Classes in this File Line Coverage Branch Coverage Complexity
XingFrame
100%
62/62
83%
15/18
1.692
 
 1  
 package org.jaudiotagger.audio.mp3;
 2  
 
 3  
 import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
 4  
 
 5  
 import java.nio.ByteBuffer;
 6  
 import java.util.Arrays;
 7  
 
 8  
 /**
 9  
  * Xing Frame
 10  
  * <p/>
 11  
  * <p>In some MP3s which variable bit rate the first frame in the file contains a special frame called a Xing Frame,
 12  
  * instead of audio data. This is used to store additional information about the file. The most important aspect for
 13  
  * this library is details allowing us to determine the bitrate of a Variable Bit Rate VBR file without having
 14  
  * to process the whole file.
 15  
  * <p/>
 16  
  * Xing VBR Tag data format is 120 bytes long
 17  
  * 4 bytes for Header Tag
 18  
  * 4 bytes for Header Flags
 19  
  * 4 bytes for FRAME SIZE
 20  
  * 4 bytes for AUDIO_SIZE
 21  
  * 100 bytes for entry (NUMTOCENTRIES)
 22  
  * 4 bytes for VBR SCALE. a VBR quality indicator: 0=best 100=worst
 23  
  * <p/>
 24  
  * It my then contain a Lame Frame ( a Lame frame is in essence an extended Xing Frame
 25  
  */
 26  
 public class XingFrame
 27  
 {
 28  
 
 29  
     //The offset into first frame varies based on the MPEG frame properties
 30  
     private static final int MPEG_VERSION_1_MODE_MONO_OFFSET = 21;
 31  
     private static final int MPEG_VERSION_1_MODE_STEREO_OFFSET = 36;
 32  
     private static final int MPEG_VERSION_2_MODE_MONO_OFFSET = 13;
 33  
     private static final int MPEG_VERSION_2_MODE_STEREO_OFFSET = 21;
 34  
 
 35  
     private static final int XING_HEADER_BUFFER_SIZE = 120;
 36  
     private static final int XING_IDENTIFIER_BUFFER_SIZE = 4;
 37  
     private static final int XING_FLAG_BUFFER_SIZE = 4;
 38  
     private static final int XING_FRAMECOUNT_BUFFER_SIZE = 4;
 39  
     private static final int XING_AUDIOSIZE_BUFFER_SIZE = 4;
 40  
 
 41  
     public static final int MAX_BUFFER_SIZE_NEEDED_TO_READ_XING = MPEG_VERSION_1_MODE_STEREO_OFFSET + XING_HEADER_BUFFER_SIZE + LameFrame.LAME_HEADER_BUFFER_SIZE;
 42  
 
 43  
 
 44  
     private static final int BYTE_1 = 0;
 45  
     private static final int BYTE_2 = 1;
 46  
     private static final int BYTE_3 = 2;
 47  
     private static final int BYTE_4 = 3;
 48  
 
 49  
     /**
 50  
      * Use when it is a VBR (Variable Bitrate) file
 51  
      */
 52  48
     private static final byte[] XING_VBR_ID = {'X', 'i', 'n', 'g'};
 53  
 
 54  
     /**
 55  
      * Use when it is a CBR (Constant Bitrate) file
 56  
      */
 57  48
     private static final byte[] XING_CBR_ID = {'I', 'n', 'f', 'o'};
 58  
 
 59  
 
 60  
     private static ByteBuffer header;
 61  48
     private static XingFrame xingFrame = null;
 62  
 
 63  425
     private boolean vbr = false;
 64  425
     private boolean isFrameCountEnabled = false;
 65  425
     private int frameCount = -1;
 66  425
     private boolean isAudioSizeEnabled = false;
 67  425
     private int audioSize = -1;
 68  
     private LameFrame lameFrame;
 69  
 
 70  
     /**
 71  
      * Read the Xing Properties from the buffer
 72  
      */
 73  
     private XingFrame()
 74  425
     {
 75  
         //Go to start of Buffer
 76  425
         header.rewind();
 77  
 
 78  
         //Set Vbr
 79  425
         setVbr();
 80  
 
 81  
         //Read Flags, only the fourth byte of interest to us
 82  425
         byte flagBuffer[] = new byte[XING_FLAG_BUFFER_SIZE];
 83  425
         header.get(flagBuffer);
 84  
 
 85  
         //Read FrameCount if flag set
 86  425
         if ((flagBuffer[BYTE_4] & (byte) (1)) != 0)
 87  
         {
 88  425
             setFrameCount();
 89  
         }
 90  
 
 91  
         //Read Size if flag set
 92  425
         if ((flagBuffer[BYTE_4] & (byte) (1 << 1)) != 0)
 93  
         {
 94  425
             setAudioSize();
 95  
         }
 96  
 
 97  
         //TODO TOC
 98  
         //TODO VBR Quality
 99  
 
 100  
         //Look for LAME Header as long as we have enough bytes to do it properly
 101  425
         if (header.limit() >= XING_HEADER_BUFFER_SIZE + LameFrame.LAME_HEADER_BUFFER_SIZE)
 102  
         {
 103  425
             header.position(XING_HEADER_BUFFER_SIZE);
 104  425
             lameFrame = LameFrame.parseLameFrame(header);
 105  
         }
 106  425
     }
 107  
 
 108  
     public LameFrame getLameFrame()
 109  
     {
 110  848
         return lameFrame;
 111  
     }
 112  
 
 113  
     /**
 114  
      * Set whether or not VBR, (Xing can also be used for CBR though this is less useful)
 115  
      */
 116  
     private void setVbr()
 117  
     {
 118  
         //Is it VBR or CBR
 119  425
         byte[] identifier = new byte[XING_IDENTIFIER_BUFFER_SIZE];
 120  425
         header.get(identifier);
 121  425
         if (Arrays.equals(identifier, XING_VBR_ID))
 122  
         {
 123  10
             MP3File.logger.finest("Is Vbr");
 124  10
             vbr = true;
 125  
         }
 126  425
     }
 127  
 
 128  
     /**
 129  
      * Set count of frames
 130  
      */
 131  
     private void setFrameCount()
 132  
     {
 133  425
         byte frameCountBuffer[] = new byte[XING_FRAMECOUNT_BUFFER_SIZE];
 134  425
         header.get(frameCountBuffer);
 135  425
         isFrameCountEnabled = true;
 136  425
         frameCount = (frameCountBuffer[BYTE_1] << 24) & 0xFF000000 | (frameCountBuffer[BYTE_2] << 16) & 0x00FF0000 | (frameCountBuffer[BYTE_3] << 8) & 0x0000FF00 | frameCountBuffer[BYTE_4] & 0x000000FF;
 137  425
     }
 138  
 
 139  
     /**
 140  
      * @return true if frameCount has been specified in header
 141  
      */
 142  
     public final boolean isFrameCountEnabled()
 143  
     {
 144  425
         return isFrameCountEnabled;
 145  
     }
 146  
 
 147  
     /**
 148  
      * @return count of frames
 149  
      */
 150  
     public final int getFrameCount()
 151  
     {
 152  425
         return frameCount;
 153  
     }
 154  
 
 155  
     /**
 156  
      * Set size of AudioData
 157  
      */
 158  
     private void setAudioSize()
 159  
     {
 160  425
         byte frameSizeBuffer[] = new byte[XING_AUDIOSIZE_BUFFER_SIZE];
 161  425
         header.get(frameSizeBuffer);
 162  425
         isAudioSizeEnabled = true;
 163  425
         audioSize = (frameSizeBuffer[BYTE_1] << 24) & 0xFF000000 | (frameSizeBuffer[BYTE_2] << 16) & 0x00FF0000 | (frameSizeBuffer[BYTE_3] << 8) & 0x0000FF00 | frameSizeBuffer[BYTE_4] & 0x000000FF;
 164  425
     }
 165  
 
 166  
     /**
 167  
      * @return true if audioSize has been specified in header
 168  
      */
 169  
     public final boolean isAudioSizeEnabled()
 170  
     {
 171  10
         return isAudioSizeEnabled;
 172  
     }
 173  
 
 174  
     /**
 175  
      * @return size of audio data in bytes
 176  
      */
 177  
     public final int getAudioSize()
 178  
     {
 179  20
         return audioSize;
 180  
     }
 181  
 
 182  
     /**
 183  
      * Parse the XingFrame of an MP3File, cannot be called until we have validated that
 184  
      * this is a XingFrame
 185  
      *
 186  
      * @return
 187  
      * @throws InvalidAudioFrameException
 188  
      */
 189  
     public static XingFrame parseXingFrame() throws InvalidAudioFrameException
 190  
     {
 191  425
         xingFrame = new XingFrame();
 192  425
         return xingFrame;
 193  
     }
 194  
 
 195  
     /**
 196  
      * IS this a Xing frame
 197  
      *
 198  
      * @return true if this is a Xing frame
 199  
      */
 200  
     public static boolean isXingFrame(ByteBuffer bb, MPEGFrameHeader mpegFrameHeader)
 201  
     {
 202  
         //We store this so can return here after scanning through buffer
 203  493
         int startPosition = bb.position();
 204  
 
 205  
         //Get to Start of where Xing Frame Should be ( we dont know if it is one at this point)
 206  493
         if (mpegFrameHeader.getVersion() == MPEGFrameHeader.VERSION_1)
 207  
         {
 208  475
             if (mpegFrameHeader.getChannelMode() == MPEGFrameHeader.MODE_MONO)
 209  
             {
 210  424
                 bb.position(startPosition + MPEG_VERSION_1_MODE_MONO_OFFSET);
 211  
             }
 212  
             else
 213  
             {
 214  51
                 bb.position(startPosition + MPEG_VERSION_1_MODE_STEREO_OFFSET);
 215  
             }
 216  
         }
 217  
         //MPEGVersion 2 and 2.5
 218  
         else
 219  
         {
 220  18
             if (mpegFrameHeader.getChannelMode() == MPEGFrameHeader.MODE_MONO)
 221  
             {
 222  15
                 bb.position(startPosition + MPEG_VERSION_2_MODE_MONO_OFFSET);
 223  
             }
 224  
             else
 225  
             {
 226  3
                 bb.position(startPosition + MPEG_VERSION_2_MODE_STEREO_OFFSET);
 227  
             }
 228  
         }
 229  
 
 230  
         //Create header from here
 231  493
         header = bb.slice();
 232  
 
 233  
         // Return Buffer to start Point
 234  493
         bb.position(startPosition);
 235  
 
 236  
         //Check Identifier
 237  493
         byte[] identifier = new byte[XING_IDENTIFIER_BUFFER_SIZE];
 238  493
         header.get(identifier);
 239  493
         if ((!Arrays.equals(identifier, XING_VBR_ID)) && (!Arrays.equals(identifier, XING_CBR_ID)))
 240  
         {
 241  68
             return false;
 242  
         }
 243  425
         MP3File.logger.finest("Found Xing Frame");
 244  425
         return true;
 245  
     }
 246  
 
 247  
     /**
 248  
      * Is this XingFrame detailing a varaible bit rate MPEG
 249  
      *
 250  
      * @return
 251  
      */
 252  
     public final boolean isVbr()
 253  
     {
 254  443
         return vbr;
 255  
     }
 256  
 
 257  
     /**
 258  
      * @return a string represntation
 259  
      */
 260  
     public String toString()
 261  
     {
 262  20
         return "xingheader" + " vbr:" + vbr + " frameCountEnabled:" + isFrameCountEnabled + " frameCount:" + frameCount + " audioSizeEnabled:" + isAudioSizeEnabled + " audioFileSize:" + audioSize;
 263  
     }
 264  
 }