package org.farng.mp3.id3; import java.io.IOException; import java.io.RandomAccessFile; import org.farng.mp3.InvalidTagException; /** * <h3>4.30.   Audio seek point index</h3> * <p/> * <p>   Audio files with variable bit rates are intrinsically difficult to<br>    deal with in the * case of seeking within the file. The ASPI frame<br>    makes seeking easier by providing a list a seek * points within the<br>    audio file. The seek points are a fractional offset within the audio<br> *    data, providing a starting point from which to find an appropriate<br> * <p/> *    point to start decoding. The presence of an ASPI frame requires the<br>    existence of a TLEN * frame, indicating the duration of the file in<br>    milliseconds. There may only be one 'audio seek point * index' frame in<br>    a tag.</p> * <p/> * <p>     <Header for 'Seek Point Index', ID: "ASPI"><br>      * Indexed data start (S)         $xx xx xx xx<br>      * Indexed data length (L)        $xx xx xx xx<br>      Number of * index points (N)     $xx xx<br> * <p/> *      Bits per index point (b)       $xx</p> * <p/> * <p>   Then for every index point the following data is included;</p> * <p/> * <p>     Fraction at index (Fi)          $xx * (xx)</p> * <p/> * <p>   'Indexed data start' is a byte offset from the beginning of the file.<br>    'Indexed data * length' is the byte length of the audio data being<br>    indexed. 'Number of index points' is the number * of index points, as<br>    the name implies. The recommended number is 100. 'Bits per index<br> *    point' is 8 or 16, depending on the chosen precision. 8 bits works<br> * <p/> *    well for short files (less than 5 minutes of audio), while 16 bits is<br>    advantageous for * long files. 'Fraction at index' is the numerator of<br>    the fraction representing a relative position in * the data. The<br>    denominator is 2 to the power of b.</p> * <p/> * <p>   Here are the algorithms to be used in the calculation. The known data<br>    must be the * offset of the start of the indexed data (S), the offset<br>    of the end of the indexed data (E), the * number of index points (N),<br>    the offset at index i (Oi). We calculate the fraction at index i<br> *    (Fi).</p> * <p/> * <p>   Oi is the offset of the frame whose start is soonest after the point<br>    for which the * time offset is (i/N * duration).</p> * <p/> * <p>   The frame data should be calculated as follows:</p> * <p/> * <p>     Fi = Oi/L * 2^b    (rounded down to the nearest integer)</p> * <p/> * <p>   Offset calculation should be calculated as follows from data in the<br>    frame:</p> * <p/> * <p>     Oi = (Fi/2^b)*L    (rounded up to the nearest integer)<br> </p> * * @author Eric Farng * @version $Revision: 2374 $ */ public class FrameBodyASPI extends AbstractID3v2FrameBody { private short[] fraction = null; private int bitsPerPoint = 0; private int dataLength = 0; private int dataStart = 0; private int indexPoints = 0; /** * Creates a new FrameBodyASPI object. */ public FrameBodyASPI() { super(); } /** * Creates a new FrameBodyASPI object. */ public FrameBodyASPI(final FrameBodyASPI copyObject) { super(copyObject); fraction = (short[]) copyObject.fraction.clone(); bitsPerPoint = copyObject.bitsPerPoint; dataLength = copyObject.dataLength; dataStart = copyObject.dataStart; indexPoints = copyObject.indexPoints; } /** * Creates a new FrameBodyASPI object. */ public FrameBodyASPI(final int dataStart, final int dataLength, final int indexPoints, final int bitsPerPoint, final short[] fraction) { super(); this.dataStart = dataStart; this.dataLength = dataLength; this.indexPoints = indexPoints; this.bitsPerPoint = bitsPerPoint; this.fraction = new short[fraction.length]; System.arraycopy(fraction, 0, this.fraction, 0, fraction.length); } /** * Creates a new FrameBodyASPI object. */ public FrameBodyASPI(final RandomAccessFile file) throws IOException, InvalidTagException { super(); read(file); } public String getIdentifier() { return "ASPI"; } public int getSize() { return 4 + 4 + 2 + 1 + fraction.length << 1; } /** * This method is not yet supported. * * @throws UnsupportedOperationException This method is not yet supported */ public void equals() { // todo Implement this java.lang.Object method throw new UnsupportedOperationException("Method equals() not yet implemented."); } protected void setupObjectList() { // throw new UnsupportedOperationException(); } public void read(final RandomAccessFile file) throws IOException, InvalidTagException { final int size = readHeader(file); if (size == 0) { throw new InvalidTagException("Empty Frame"); } dataStart = file.readInt(); dataLength = file.readInt(); indexPoints = (int) file.readShort(); bitsPerPoint = (int) file.readByte(); fraction = new short[indexPoints]; for (int i = 0; i < indexPoints; i++) { if (bitsPerPoint == 8) { fraction[i] = (short) file.readByte(); } else if (bitsPerPoint == 16) { fraction[i] = file.readShort(); } else { throw new InvalidTagException("ASPI bits per point wasn't 8 or 16"); } } } public String toString() { return getIdentifier() + ' ' + this .dataStart + ' ' + this .dataLength + ' ' + this .indexPoints + ' ' + this .bitsPerPoint + ' ' + this.fraction .toString(); } public void write(final RandomAccessFile file) throws IOException { writeHeader(file, getSize()); file.writeInt(dataStart); file.writeInt(dataLength); file.writeShort(indexPoints); file.writeByte(16); for (int i = 0; i < indexPoints; i++) { file.writeShort((int) fraction[i]); } } }