1 package org.farng.mp3.id3; 2 3 import org.farng.mp3.InvalidTagException; 4 5 import java.io.IOException; 6 import java.io.RandomAccessFile; 7 8 /** 9 * <h3>4.30. Audio seek point index</h3> 10 * <p/> 11 * <p> Audio files with variable bit rates are intrinsically difficult to<br> deal with in the 12 * case of seeking within the file. The ASPI frame<br> makes seeking easier by providing a list a seek 13 * points within the<br> audio file. The seek points are a fractional offset within the audio<br> 14 * data, providing a starting point from which to find an appropriate<br> 15 * <p/> 16 * point to start decoding. The presence of an ASPI frame requires the<br> existence of a TLEN 17 * frame, indicating the duration of the file in<br> milliseconds. There may only be one 'audio seek point 18 * index' frame in<br> a tag.</p> 19 * <p/> 20 * <p> <Header for 'Seek Point Index', ID: "ASPI"><br> 21 * Indexed data start (S) $xx xx xx xx<br> 22 * Indexed data length (L) $xx xx xx xx<br> Number of 23 * index points (N) $xx xx<br> 24 * <p/> 25 * Bits per index point (b) $xx</p> 26 * <p/> 27 * <p> Then for every index point the following data is included;</p> 28 * <p/> 29 * <p> Fraction at index (Fi) $xx 30 * (xx)</p> 31 * <p/> 32 * <p> 'Indexed data start' is a byte offset from the beginning of the file.<br> 'Indexed data 33 * length' is the byte length of the audio data being<br> indexed. 'Number of index points' is the number 34 * of index points, as<br> the name implies. The recommended number is 100. 'Bits per index<br> 35 * point' is 8 or 16, depending on the chosen precision. 8 bits works<br> 36 * <p/> 37 * well for short files (less than 5 minutes of audio), while 16 bits is<br> advantageous for 38 * long files. 'Fraction at index' is the numerator of<br> the fraction representing a relative position in 39 * the data. The<br> denominator is 2 to the power of b.</p> 40 * <p/> 41 * <p> Here are the algorithms to be used in the calculation. The known data<br> must be the 42 * offset of the start of the indexed data (S), the offset<br> of the end of the indexed data (E), the 43 * number of index points (N),<br> the offset at index i (Oi). We calculate the fraction at index i<br> 44 * (Fi).</p> 45 * <p/> 46 * <p> Oi is the offset of the frame whose start is soonest after the point<br> for which the 47 * time offset is (i/N * duration).</p> 48 * <p/> 49 * <p> The frame data should be calculated as follows:</p> 50 * <p/> 51 * <p> Fi = Oi/L * 2^b (rounded down to the nearest integer)</p> 52 * <p/> 53 * <p> Offset calculation should be calculated as follows from data in the<br> frame:</p> 54 * <p/> 55 * <p> Oi = (Fi/2^b)*L (rounded up to the nearest integer)<br> </p> 56 * 57 * @author Eric Farng 58 * @version $Revision: 1.5 $ 59 */ 60 public class FrameBodyASPI extends AbstractID3v2FrameBody { 61 62 private short[] fraction = null; 63 private int bitsPerPoint = 0; 64 private int dataLength = 0; 65 private int dataStart = 0; 66 private int indexPoints = 0; 67 68 /** 69 * Creates a new FrameBodyASPI object. 70 */ FrameBodyASPI()71 public FrameBodyASPI() { 72 super(); 73 } 74 75 /** 76 * Creates a new FrameBodyASPI object. 77 */ FrameBodyASPI(final FrameBodyASPI copyObject)78 public FrameBodyASPI(final FrameBodyASPI copyObject) { 79 super(copyObject); 80 fraction = (short[]) copyObject.fraction.clone(); 81 bitsPerPoint = copyObject.bitsPerPoint; 82 dataLength = copyObject.dataLength; 83 dataStart = copyObject.dataStart; 84 indexPoints = copyObject.indexPoints; 85 } 86 87 /** 88 * Creates a new FrameBodyASPI object. 89 */ FrameBodyASPI(final int dataStart, final int dataLength, final int indexPoints, final int bitsPerPoint, final short[] fraction)90 public FrameBodyASPI(final int dataStart, 91 final int dataLength, 92 final int indexPoints, 93 final int bitsPerPoint, 94 final short[] fraction) { 95 super(); 96 this.dataStart = dataStart; 97 this.dataLength = dataLength; 98 this.indexPoints = indexPoints; 99 this.bitsPerPoint = bitsPerPoint; 100 this.fraction = new short[fraction.length]; 101 System.arraycopy(fraction, 0, this.fraction, 0, fraction.length); 102 } 103 104 /** 105 * Creates a new FrameBodyASPI object. 106 */ FrameBodyASPI(final RandomAccessFile file)107 public FrameBodyASPI(final RandomAccessFile file) throws IOException, InvalidTagException { 108 super(); 109 read(file); 110 } 111 getIdentifier()112 public String getIdentifier() { 113 return "ASPI"; 114 } 115 getSize()116 public int getSize() { 117 return 4 + 4 + 2 + 1 + fraction.length << 1; 118 } 119 120 /** 121 * This method is not yet supported. 122 * 123 * @throws UnsupportedOperationException This method is not yet supported 124 */ equals()125 public void equals() { 126 // todo Implement this java.lang.Object method 127 throw new UnsupportedOperationException("Method equals() not yet implemented."); 128 } 129 setupObjectList()130 protected void setupObjectList() { 131 // throw new UnsupportedOperationException(); 132 } 133 read(final RandomAccessFile file)134 public void read(final RandomAccessFile file) throws IOException, InvalidTagException { 135 final int size = readHeader(file); 136 if (size == 0) { 137 throw new InvalidTagException("Empty Frame"); 138 } 139 dataStart = file.readInt(); 140 dataLength = file.readInt(); 141 indexPoints = (int) file.readShort(); 142 bitsPerPoint = (int) file.readByte(); 143 fraction = new short[indexPoints]; 144 for (int i = 0; i < indexPoints; i++) { 145 if (bitsPerPoint == 8) { 146 fraction[i] = (short) file.readByte(); 147 } else if (bitsPerPoint == 16) { 148 fraction[i] = file.readShort(); 149 } else { 150 throw new InvalidTagException("ASPI bits per point wasn't 8 or 16"); 151 } 152 } 153 } 154 toString()155 public String toString() { 156 return getIdentifier() + ' ' + this 157 .dataStart + ' ' + this 158 .dataLength + ' ' + this 159 .indexPoints + ' ' + this 160 .bitsPerPoint + ' ' + this.fraction 161 .toString(); 162 } 163 write(final RandomAccessFile file)164 public void write(final RandomAccessFile file) throws IOException { 165 writeHeader(file, getSize()); 166 file.writeInt(dataStart); 167 file.writeInt(dataLength); 168 file.writeShort(indexPoints); 169 file.writeByte(16); 170 for (int i = 0; i < indexPoints; i++) { 171 file.writeShort((int) fraction[i]); 172 } 173 } 174 }