package com.limegroup.gnutella.metadata; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import com.limegroup.gnutella.ByteOrder; /** * Provide MP3 file info derived from the file header data * * @see #getLayer_* -> mp3 layer (3 or "Layer III", etc) * @see #getMode -> mode type strings (stereo, dual channel, etc) * @see #getFrequency -> available frequencies (32000, 44100, etc) khz * @see #getVersion_* -> mp3 file version (2.0, or "MPEG Version 2.0") * @see #getHeaderBitRate -> constant bit rates(CBR) (128, 256, etc) kps * @see com.limegroup.gnutella.ByteOrder * * @author cHANCE mOORE, ctmoore [at] gottapee [dot] com - 30 July 2002 * One of the Sindhis (both?), limewire team * Gustav "Grim Reaper" Munkby, grd@swipnet.se * * TODO: add tests? */ //34567890123456789012345678901234567890123456789012345678901234567890123456789 public final class MP3Info { /** * the canonical localized mp3 file name */ private final String _file; /** * 1st mp3 file's header; 4 bytes(combined) at beginning after any ID tags * all the standard getters reference the header data */ private int _header; /** * represenation of the Variable bit rate header, if one exists * @see MP3Info$VBRHeader */ private VBRHeader _vbrHeader; /** * Data holder for Xing variable bit rate headers */ final class VBRHeader { /** * initially -1 as most fields are optional * fields without getters are accessed through the MP3Info */ private int numFrames = -1; private int numBytes = -1; private int scale = -1; private byte[] toc; /** * @return int suggested encoding quality, scaled 1 to 100 */ int getScale() { return scale; } /** * Table of Contents holds byte pos -> % of song complete, 1 to 100% * @return byte[] These bytes are raw java bytes, need to be "& 255" */ byte[] getTableOfContents() { return toc; } /** * VBR header only returns a rate when frames and bytes are supplied */ int getBitRate() { if (numFrames != -1 && numBytes != -1) { double tpf = 0; switch (getLayerIndex()) { case 1: case 2: tpf = 1152D; break; case 3: tpf = 384D; } //new double[]{ -1, 1152, 1152, 384 } tpf /= getFrequency(); if( (getVersion_Numeric() == 2) || //MPEG_V_2 (getVersion_Numeric() == 0) ) { //MPEG_V_25 tpf /= 2; } return (int)( (numBytes * 8) / (tpf * numFrames * 1000) ); } return -1; } /** * */ int getLengthInSeconds() { if (numFrames != -1) { double tpf = 0; switch (getLayerIndex()) { case 1: case 2: tpf = 1152D; break; case 3: tpf = 384D; } //new double[]{ -1, 1152, 1152, 384 } tpf /= getFrequency(); if( (getVersion_Numeric() == 2) || //MPEG_V_2 (getVersion_Numeric() == 0) ) { //MPEG_V_25 tpf /= 2; } return (int)( tpf * numFrames ); } return -1; } } /** * An MPEG audio file is built up from smaller parts called frames, which * are generally independent items. Each frame has its own header and audio * data that follows. There is NO MPEG file header; therefore, you can cut * any part of MPEG file and play it correctly (cut on frame boundaries!), * excluding MPEG 1 Layer III frames which are often dependent on another. * * To read info about an MPEG file, you can find the first frame, read its * header and assume that the other frames are the same. Exceptions to this * are VBR (variable bit rate) and ABR (average bit rate) files. The frame * header is constituted by the very first four bytes (32bits) in a frame. * The first 11 bits are always set on(1) and they're called "frame sync". * Frame CRC is optional and 16 bits long; it follows the frame header. * After the CRC comes the audio data. * * ::EXAMPLE:: MP3 file header format (4 byte length or 32 bits) * byte[4] = { -1, -5, 80, 108 } * -1 << 24 + -5 << 16 + 80 << 08 + 108 << 0 {HdrCRC} * 11111111 11101010 00110000 11000000 {0000} * AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM {ZZZZ} * * Label, Position(bits), Description * A (31-21) Frame sync * All bits set (1) * B (20,19) MPEG Audio version ID * 00 - MPEG Ver 2.5, 01 - reserved, 10 - Ver 2, 11 - Ver 1 * Note: MPEG Ver 2.5 is not official; bit # 20 indicates 2.5 * C (18,17) Layer description * 00 - reserved, 01 - Layer III, 10 - Layer II, 11 - Layer I * D (16) Protection bit * 0 - None, 1 - Protected by CRC (16bit crc follows header) * E (15,12) Bitrate index, version and layer * bits V1,L1 V1,L2 V1,L3 V2,L1 V2, L2 & L3 * F (11,10) * G (9) Padding bit * 0 - frame not padded, 1 - frame padded with one extra slot * Note: Padding is used to fit the bit rates exactly. * H (8) Private bit * 0 - not private, 1 - private * Note: May be freely used for other needs of an application. * I (7,6) Channel Mode * 00 - Stereo, 01 - Joint stereo, 10 - Dual (Stereo), 11 - Mono * J (5,4) Mode extension (Only if Joint stereo) * Used to join data; bits dynamically generated by an encoder. * K (3) Copyright * 0 - Audio is not copyrighted, 1 - Audio is marked copyrighted * L (2) Original * 0 - Copy of original media, 1 - Original media * M (1,0) Emphasis * 00 - none, 01 - 50/15 ms, 10 - reserved, 11 - CCIT J.17 * Z (32-35) CRC !!OPTIONAL!! * Note: NOT part of header, just appended on end when needed * * We read in bytes from the beginning of the mp3 file looking for * the 4 byte header; we can't assume it starts at byte 0 because * ID3 tags may be prepended before the first valid header. * The loop below strolls through buffered chunks of the file * looking for the header. As an optimization, we check the first * 10 bytes initially as it may contain the header; if it doesn't * we then check the first 10 bytes for an ID3v2 header and fetch * the tag's length, skipping those bytes leading us directly * to the header. If neither are found, it's a brute force search. * With each chunk, we step forward one byte at a time, and test * the current byte plus the next 3 bytes for a valid mp3 header. * * @exception java.io.IOException mp3 fileName had no valid header */ public MP3Info(String file) throws IOException { _file = file; //TODO:use 1.4 BufferMaps int i = 0; //reusable loop variant int pos = 0; //position in file, start at the beginning, duh... int adjustedEOB = 0;//adjusted end depending on actual bytes read int c = 0; //number of actual bytes read from file FileInputStream fis = null; byte[] buf = new byte[2048]; try { fis = new FileInputStream(_file); //initially check the first few bytes c = fis.read(buf, 0, buf.length); if( c < 4 ) throw new IOException("early EOF, tiny file?"); //check for ID3 tag //officially ID3, some tags incorrectly contain lowercase if ( (buf[0] == 'i' || buf[0] == 'I') && (buf[1] == 'd' || buf[1] == 'D') && (buf[2] == '3') ) { //length of tag format is specified in the ID3v2 standard //28 bits amongst four bytes, first bit of each byte is 0 i = buf[6] << 7 | buf[7] << 7 | buf[8] << 7 | buf[9]; if (i > 0) { //skip indicated tag length and read header i += 10; } else if (i < 0) { //clear bad data i = 0; } } endheadersearch: do { if (pos < buf.length - 3) { //is first time? adjustedEOB = c - 3; } else { i = 0; //reset i except first time adjustedEOB = c; //already offset } for ( ; i < adjustedEOB; i++ ) { /////// //quicktest, first byte must be 256 //quickly skip more expensive tests below, if possible if (buf[i] != -1 || (buf[i+1] & 255) < 224) { continue; } //build a header to test _header = ( ByteOrder.ubyte2int(buf[i+3]) ) | ( ByteOrder.ubyte2int(buf[i+2]) << 8 ) | ( ByteOrder.ubyte2int(buf[i+1]) << 16 ) | ( ByteOrder.ubyte2int(buf[i ]) << 24 ) ; // detect if valid header or just four different chars if ( //(getFrameSync() ==2047) && //tested above (getVersionIndex() != 1) && (getLayerIndex() != 0) && (getBitrateIndex() != 0) && (getBitrateIndex() != 15) && (getFrequencyIndex() != 3) && (getEmphasisIndex() != 2) ) { pos += i; break endheadersearch; } } //save last 3 bytes to test with next chunk // check trailing end of last chunk with start of new chunk // ie. last 3 bytes with first new byte // last 2 bytes with first 2 new bytes // last 1 byte with first 3 new bytes if (adjustedEOB != -1) { //skip when EOF buf[0] = buf[c-3]; buf[1] = buf[c-2]; buf[2] = buf[c-1]; } pos += c - 3; c = fis.read(buf, 3, buf.length-3); //read next chunk if( c < 6 ) //not enough to make a difference throw new IOException("MP3 Header not found."); } while (c != -1 && pos < 100000); //c is # of bytes read; until EOF //stop checking after first 100k, could be corrupted/infected file if (c == -1 || pos >= 100000) { // what the $#*! _header = 0; throw new IOException("MP3 header not found."); } // Looking for the VBR // @see loadVBRHeeader for VBR format specifics // advance to check where Xing header would be // make sure we have enough data to test/work with // 120 is total 'possible' VBR length // 36 max bytes to skip // 3 is to cover length of VBR header int need = buf.length // total we have. - i // where we're currently at - 3 // VBR header - 120 // max possible VBR length - 36; // max bytes to skip if (need < 0) { //special case, we need more data need = -need; // flip need to be positive. i -= need; // shift our offset down by the amount we'll be moving int j = 0; for (; need < buf.length; j++, need++ ) { // shift data buf[j] = buf[need]; } // IMPORTANT: // j is NOT equal to i for the following reason: // i is where we last stopped reading data from the buffer. // j is where the last bit of valid information in the buffer is. // we must continue reading from the buffer using i, but we must // fill up the the rest of the buffer from j on. //read more, starting at where we last have valid data. c = fis.read(buf, j, buf.length-j); } if ( getVersionIndex() == 3 ) { // mpeg version 1 i += (getModeIndex()==3 ? 21 : 36); } else { // mpeg version 2 or 2.5 i += (getModeIndex()==3 ? 23 : 21); } // Doh!! not all VBR files will have correct tags, it's optional switch (buf[i+0]) { case 88: //'X': if (((buf[i+1] == 'i' || buf[i+1] == 'I') && (buf[i+2] == 'n' || buf[i+2] == 'N') && (buf[i+3] == 'g' || buf[i+3] == 'G'))) // The Xing VBR headers always begin with the four chars "Xing" loadXingHeader(buf, i+4); break; case 86: //'V': if ((buf[i+1] == 'B' && buf[i+2] == 'R' && buf[i+3] == 'I' )) //"VBRI" is a rarely used method of tagging Fhg encoded VBRs loadFhgHeader(buf, i+4); break; //case 73: //'I': // if( buf[i+1] == 'n' // && buf[i+2] == 'f' // && buf[i+3] == 'o' ) // LAME uses "Info" to tag LAME CBR/ABR files // there is no VBR data, but may provide useful LAME & ABR data // 4 skips VBR header, 109 skips dead Xing tag to reach 'LAME' tag // loadLAMETag(buf, i+4+109); //break; //default: //true VBR file may not have a proper tag, to find out for sure //read every header to calculate true variable rate, length, etc } } finally { //cleanup try { if( fis != null ) fis.close(); } catch (IOException e) {}//ignore } } public int getBitRate() { if (hasVariableBitRate()) { int i = _vbrHeader.getBitRate(); if (i != -1) { return i; } } long size = getFileSize(); double mediumFrameSize = ( (getLayerIndex() == 3 ? 12000 : 144000) * getHeaderBitRate() ) / ( (double)getFrequency() ); /* FrameSizes formula mpeg v1: FrameSize = 12 * BitRate / SampleRate + Padding mpeg v2: FrameSize = 144 * BitRate / SampleRate + Padding bitrate is kbps & sample rate in Hz, so multiply BitRate by 1000 */ // get average frame size by dividing size by the # of frames int retInt = (int)( (size / (size/mediumFrameSize) * getFrequency()) / (getLayerIndex() == 3 ? 12000 : 144000) ); //???? If computed bitrate is nonsensical, just use header bitrate if (retInt < 1) { return getHeaderBitRate(); } else { return retInt; } } private int getBitrateIndex() { return _header >> 12 & 15; } /** * Mp3 Emphasis * -> "none", "50/15 ms", null, "CCIT J.17" * * @see #getEmphasisIndex * @return java.lang.String string reprensentation of emphasis */ public String getEmphasis() { switch (getEmphasisIndex()) { case 0: return "none"; case 1: return "50/15 ms"; case 2: return null; case 3: return "CCIT J.17"; default: //not an official tag return "<unknown>"; } } private int getEmphasisIndex() { return _header & 3; } /** * Bytes in the mp3 file * * @return long */ public long getFileSize() { if (hasVariableBitRate() && _vbrHeader.numBytes != -1) { return _vbrHeader.numBytes; } return new File(_file).length(); } private int getFrameSync() { return _header >> 21 & 2047; } /** * The frequency is dependent on bitrate index and MPEG version * -> MPEG 2.5 - 32000, 16000, 8000 * -> MPEG 2 - 22050, 24000, 16000 * -> MPEG 1 - 44100, 48000, 32000 * * @see #getVersionIndex * @see #getFrequencyIndex * @return the current frequency [8000-48000 Hz] */ public int getFrequency() { switch (getVersionIndex()) { case 0: //MPEG 2.5 - 32000, 16000, 8000 switch(getFrequencyIndex()) { case 0: return 11025; //!!32000 isn't correct!! case 1: return 12000; //!!16000 isn't correct!! case 2: return 8000; default: return -1;//error } case 1: //reserved return 0; case 2: //MPEG 2 - 22050, 24000, 16000 switch(getFrequencyIndex()) { case 0: return 22050; case 1: return 24000; case 2: return 16000; default: return -1;//error } case 3: //MPEG 1 - 44100, 48000, 32000 switch(getFrequencyIndex()) { case 0: return 44100; case 1: return 48000; case 2: return 32000; default: return -1;//error } default: //error return -1; } } private int getFrequencyIndex() { return _header >> 10 & 3; } /** * Based on the bitrate index found in the header * The header bit rate is based off the BITRATE_TABLE values using indexes * whereas the other bit rate is calculated directly without the table * both rates should be equal, excluding possible VBR discrepencies * * @see getBitRate * @return int The bitrate in between 8 - 448 Kb/s . */ public int getHeaderBitRate() { int ind = -1; switch (getVersionIndex()) { case 0: //2.0 case 2: //2.5 if( getLayer_Numeric() == 1 ) { // mpeg layer 1 ind = 3; } else {// mpeg layer 2 & 3 if( layer == 2 || layer == 3 ) { ind = 4; } break; case 1: //error or nothing default: return 0; case 3: ind = getLayer_Numeric()-1; //if( layer == MPEG_L_1 ) ind = 0; //else if( layer == MPEG_L_2 ) ind = 1; //else if( layer == MPEG_L_3 ) ind = 2; } //if( bitrateIndex >= 0 && bitrateIndex <= 15 ) { try { short[] BITRATE_TABLE = { 0, 0, 0, 0, 0, 32, 32, 32, 32, 8, 64, 48, 40, 48, 16, 96, 56, 48, 56, 24, 128, 64, 56, 64, 32, 160, 80, 64, 80, 40, 192, 96, 80, 96, 48, 224, 112, 96, 112, 56, 256, 128, 112, 128, 64, 288, 160, 128, 144, 80, 320, 192, 160, 160, 96, 352, 224, 192, 176, 112, 384, 256, 224, 192, 128, 416, 320, 256, 224, 144, 448, 384, 320, 256, 160}; return BITRATE_TABLE[getBitrateIndex()*5+ind]; } catch (ArrayIndexOutOfBoundsException aiob) { return -1; } } /** * Layer formula: * 4 - layerIndex * -> 1, 2, 3 * * @see #getLayerIndex * @return int the Layer [1-3] in small int format */ public int getLayer_Numeric() { return 4 - getLayerIndex(); } /** * Layer formula: * 4 - layerIndex * -> null, "Layer III", "Layer II", "Layer I" * * @see #getLayerIndex * @return java.lang.String representation of Mp3 Layer */ public String getLayer_String() { switch (getLayerIndex()) { //for those not in the know...don't worry //the "Layer " string is reused internally bytecode case 1: return "Layer " + "III"; case 2: return "Layer " + "II"; case 3: return "Layer " + "I"; default: return "Layer " + "?"; } } private int getLayerIndex() { return _header >> 17 & 3; } /** * Length in seconds formula: * -> fileSize / (bitrate * 100 / 8) * * @see #getFileSize * @see #getHeaderBitRate * @return long mp3 seconds */ public long getLengthInSeconds() { if (hasVariableBitRate()) { int i = _vbrHeader.getLengthInSeconds(); if (i != -1) { return i; } } return getFileSize() / (getHeaderBitRate()*1000 / 8); } /** * Output channel information * "Stereo", "Joint Stereo", "Dual Channel", "Single Channel" * * @see #getModeIndex * @return java.lang.String Display representation of playing mode */ public String getMode() { switch(getModeIndex()) { case 0: return "Stereo"; case 1: return "Joint Stereo"; case 2: return "Dual Channel"; case 3: return "Single Channel"; default: return "<unknown>"; } } /** * Mode extension joins information not used for stereo effect, thus * reducing needed resources. These bits are dynamically determined by an * encoder in Joint stereo mode. Complete frequency range of MPEG files is * divided in 32 subbands. For Layer I & II, bits determine frequency range * where intensity stereo is applied. For Layer III, two bits determine * whether intensity stereo or m/s stereo is used. * * Layer I and II | Layer III * ----------------------------------------------------- * value | Layer I & II | Intensity stereo | MS stereo * -----------------------|----------------------------- * 00 | bands 4 to 31 | off | off * 01 | bands 8 to 31 | on | off * 10 | bands 12 to 31 | off | on * 11 | bands 16 to 31 | on | on */ private int getModeExtIndex() { return _header >> 4 & 3; } private int getModeIndex() { return _header >> 6 & 3; } /** * FrameSize formula * mpeg v1: FrameSize = 12 * BitRate / SampleRate + Padding * mpeg v2: FrameSize = 144 * BitRate / SampleRate + Padding * bitrate is kbps and sample rate in Hz, so multiply BitRate by 1000 * Number of Frames formula * mp3 file length in bytes / frame size * the VBR header usually has the number of frames stored internally * * !!Results may not be precise as frame calculation is not always exact. * Programs like Winamp occasionaly return slightly different results. * For example, we don't exclude added frames like ID3 tags. * * @!deprecated Not used internally * @return int frames calculated from mp3 (possible vbr) header */ public int getNumberOfFrames() { if (hasVariableBitRate() && _vbrHeader.numFrames != -1) { return _vbrHeader.numFrames; } //getHeaderBitRate() //we round the calculation using (int) which produces a result //similiar to Winamp stats, but this breaks other calcs elsewhere return (int) ( getFileSize() / ( getLayerIndex() == 3 ? 12000 : 144000 * getBitRate() / getFrequency() + (isPadded() ? getLayerIndex() == 3 ? 32 : 8 : 0) ) ); } /** * VBR header containing Table of Contents and Quality * * @return MP3Info.VBRHeader Variable Bit Rate header */ public MP3Info.VBRHeader getVBRHeader() { return _vbrHeader; } /** * Based on the version index * -> 2.5, 0.0, 2.0, 1.0 * * @see #getVersionIndex * @return double the MPEG version number */ public double getVersion_Numeric() { switch (getVersionIndex()) { case 0: return 2.5; case 1: default: return 0.0; case 2: return 2.0; case 3: return 1.0; } } /** * Based on the version index * -> "MPEG Version 2.5", null, "MPEG Version 2.0", "MPEG Version 1.0" * * @see #getVersionIndex * @return java.lang.String representation of version */ public String getVersion_String() { switch (getVersionIndex()) { //for those not in the know...don't worry //the "Layer " string is reused internally bytecode case 0: return "MPEG Version " + "2.5"; case 1: return null; case 2: return "MPEG Version " + "2.0"; case 3: return "MPEG Version " + "1.0"; default: return "MPEG Version " + "?"; } } private int getVersionIndex() { return _header >> 19 & 3; } /** * Whether the bits per frame are not constant * * @return True if this file has a VBR */ public boolean hasVariableBitRate() { return _vbrHeader != null; } /** * Whether the copyright bit is flagged in the mp3 header * * @return boolean true if flag found */ public boolean isCoprighted() { return (_header >> 3 & 1) != 0; } /** * Whether the original bit is flagged in the mp3 header * * @return boolean true if flag found */ public boolean isOriginal() { return (_header >> 2 & 1) != 0; } /** * Whether padding bit is set; Padding is used to fit bit rates exactly. * :Example: 128k 44.1kHz layer II uses a lot of 418 bytes and some of * 417 bytes long frames to get the exact 128k bitrate. For Layer I * slot is 32 bits long, Layer II and Layer III slot is 8 bits long. * * @return boolean true if flag found */ public boolean isPadded() { return (_header >> 9 & 1) != 0; } /** * Whether the private bit is flagged in the mp3 header * * @return boolean true if flag found */ public boolean isPrivate() { return (_header >> 8 & 1) != 0; } /** * Whether the protection bit is flagged in mp3 header * Indicates CRC; 16 bit crc follows file header * * @return boolean true if flag found */ public boolean isProtected() { //CRC protection is ON when bit is not set return (_header >> 16 & 1) == 0; } /** * Whether this MP3 is embedded in a WAV file * * RIFF(Resource Interchange File Format) is a tagged file structure * developed for multimedia resource files. The structure of RIFF * is similar to the structure of an ElectronicArts IFF file. RIFF is * not actually a file format itself (since it does not represent a * specific kind of information), but its name contains the words * `interchange file format' in recognition of its roots in IFF. * * ::the beginning of file will start as follows:: * RIFF õY 1 WAVE fmt * AAAA BBBB CCCC DDDD * * A 4 bytes RIFF Tag * B 4 bytes File Size - Ignored for this test * C 4 bytes WAVE Tag * D 4 bytes fmt name * * @return boolean true if file is marked as Replay Gain RIFF-WAV * !!Doesn't gurantee file is a valid or playable RIFF-WAV */ public boolean isRiffWav() { //the results of this test are not persisted on the object //there's little benefit for a method that may never be used boolean result = false; FileInputStream fis = null; try { //safety fis = new FileInputStream(_file); byte[] buffer = new byte[16]; fis.read(buffer); result = buffer[ 0] == 'R' && buffer[ 1] == 'I' && buffer[ 2] == 'F' && buffer[ 3] == 'F' && buffer[ 8] == 'W' && buffer[ 9] == 'A' && buffer[10] == 'V' && buffer[11] == 'E' && buffer[12] == 'f' && buffer[13] == 'm' && buffer[14] == 't' && buffer[15] == ' '; } catch(IOException ignored) { // not a riff. } finally { if( fis != null ) { try { fis.close(); } catch(IOException ioe) {} } } return result; } /** * The LAME tag is not really all that lame * Added when using the LAME opensource MP3 encoder, the tag provides * song details, most importatnly for us would be any bit rate info. * * ::Example:: LAME Tag * * 0005 LAME3.90. õY a kY 1 õY q 7Y 3 dY 2 pY i 0 * ZZZZ AA.....AA BCDD DDEE FFGH IIIJ KLLL LMMN N * * Z 4 bytes VBR quality * the last part of Xing tag, is included in LAME tag * A 20 bytes LAME Tag * may not use all 20 bytes; example: 'LAME3.12 (beta 6)' * B 1 byte LAME Tag revision + VBR method * no vbr/cbr, abr, vbr-old/vbr-rh, vbr-mtrh, vbr-new/vbr-mt * C 1 byte Lowpass filter value * divided by 100 * D 4 bytes Replay Gain * see http://www.david.robinson.org/replaylevel/ * E 2 bytes Radio Replay Gain * required to make all tracks equal loudness * F 2 bytes Audiophile Replay Gain * required to give ideal listening loudness * G 1 byte Encoding flags + ATH Type * --nspsytune, --nssafejoint, --nogap (combination) * H 1 byte ABR {specified bitrate} or {minimal bitrate} * if the file is NOT an ABR file then (CBR/VBR) * I 3 bytes Encoder delays * samples added at start & padded at end complete last frame * J 1 byte Misc * noise shaping, stereo mode, optimal quality, sample freq * K 1 byte MP3 Gain * mp3 amplification factor * L 4 bytes Music Length * file size minus additional tags * M 2 bytes Music CRC * CRC-16 of mp3 music data as made originally by LAME * N 2 bytes CRC-16 of LAME Tag * CRC-16 of first 190 bytes of the VBR header frame * * @deprecated */ /* private void loadLAMETag (byte buf[], int offset) { try { } catch (Throwable t) {} //bombed trying to build LAME tag } */ /** * MPEG files frame bitrates may change in a variable bitrate (VBR). Each * frame is encoded at a different rate to maximaize quality/file size. * 1. by bitrate switching: each frame may be created differently. * 2. by bit reservoir: bits borrowed/given to other frames where needed. * * !!NOTE!! All Fhg files encode 160kb into the first mp3 header * * ::Example:: Fhg VBR Tag, bytes after header flag are optional flag * * VBRI 01949 0212 36-K pS12 0102 j80d 0....1 * AAAA BBBB CCDD DDEE EEFF GGGG HHII I....I * * A 4 bytes Header Tag * "VBRI" * B 4 bytes Header / Version Flags * 4 possible flags, determines what data follows (last bit) * * OPTIONAL C-G according to flags * C 2 bytes VBR Scale * A VBR quality indicator: 0=best 100=worst * D 4 bytes # of Bytes Per Frame / Stream Size * * E 4 bytes MPEG File Frame Size * * F 2 bytes Number of seek offsets * * G 4 bytes unknown * * H 2 bytes offset "stride" (number of frames between offsets) * * I F*2 bytes Table of Contents (TOC) * seek offsets 0-F (from beginning of file) * */ private void loadFhgHeader (byte buf[], int pos) { _vbrHeader = new MP3Info.VBRHeader(); _vbrHeader.scale = ByteOrder.ubyte2int(buf[pos+=2]); _vbrHeader.numBytes = ((ByteOrder.ubyte2int(buf[++pos]) << 24) + (ByteOrder.ubyte2int(buf[++pos]) << 16) + (ByteOrder.ubyte2int(buf[++pos]) << 8) + (ByteOrder.ubyte2int(buf[++pos]) )); _vbrHeader.numFrames =((ByteOrder.ubyte2int(buf[++pos]) << 24) + (ByteOrder.ubyte2int(buf[++pos]) << 16) + (ByteOrder.ubyte2int(buf[++pos]) << 8) + (ByteOrder.ubyte2int(buf[++pos]) )); /* TOC ignored [format is sketchy] byte b = (byte)ByteOrder.ubyte2int(buf[pos+=3]); if((b & (byte)(1 << 2 )) != 0 ) { _vbrHeader.seek =((ByteOrder.ubyte2int(buf[++pos]) << 8) + (ByteOrder.ubyte2int(buf[++pos]) )) _vbrHeader.toc = new byte[100]; System.arraycopy(buf, ++pos, _vbrHeader.toc, 0, f); } */ } /** * MPEG files frame bitrates may change in a variable bitrate (VBR). Each * frame is encoded at a different rate to maximaize quality/file size. * 1. by bitrate switching: each frame may be created differently. * 2. by bit reservoir: bits borrowed/given to other frames where needed. * * ::Example:: Xing VBR Tag, bytes after header flag are optional flag * * Xing 0007 0254 1236 12...21 0058 * AAAA BBBB CCCC DDDD FF...FF GGGG * * A 4 bytes Header Tag * "Xing" or possibly "FBRI" {"Info" is also possible in CBR} * B 4 bytes Header Flags * 4 possible flags, determines what data follows (last bit) * * OPTIONAL C-G according to flags * C 4 bytes MPEG File Frame Size * * D 4 bytes # of Bytes Per Frame / Stream Size * * F 100 bytes Table of Contents (TOC) * TOC is a 100-byte array that tells a player how many 256ths * of the file to jump to find a particular point -in percent. * :Example: jump to half-way (50%) point in 3,000,000 byte * file, then look at the 50th entry in the TOC which is 130. * Seek to 130/256*3000000=1523438th byte, scan to next frame. * G 4 bytes VBR Scale * A VBR quality indicator: 0=best 100=worst */ private void loadXingHeader (byte buf[], int offset) { _vbrHeader = new MP3Info.VBRHeader(); byte b = (byte)ByteOrder.ubyte2int(buf[offset+=3]); if ((b & 1) != 0) { _vbrHeader.numFrames =((ByteOrder.ubyte2int(buf[++offset]) << 24) + (ByteOrder.ubyte2int(buf[++offset]) << 16) + (ByteOrder.ubyte2int(buf[++offset]) << 8) + (ByteOrder.ubyte2int(buf[++offset]) )); } if((b & 2) != 0 ) { _vbrHeader.numBytes = ((ByteOrder.ubyte2int(buf[++offset]) << 24) + (ByteOrder.ubyte2int(buf[++offset]) << 16) + (ByteOrder.ubyte2int(buf[++offset]) << 8) + (ByteOrder.ubyte2int(buf[++offset]) )); } if((b & 4) != 0 ) { _vbrHeader.toc = new byte[100]; System.arraycopy(buf, ++offset, _vbrHeader.toc, 0, 100); offset += 99; } if((b & 8) != 0 ) { _vbrHeader.scale = ByteOrder.ubyte2int(buf[offset+=4]); } } }