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.&nbsp;&nbsp; Audio seek point index</h3>
10  * <p/>
11  * <p>&nbsp;&nbsp; Audio files with variable bit rates are intrinsically difficult to<br> &nbsp;&nbsp; deal with in the
12  * case of seeking within the file. The ASPI frame<br> &nbsp;&nbsp; makes seeking easier by providing a list a seek
13  * points within the<br> &nbsp;&nbsp; audio file. The seek points are a fractional offset within the audio<br>
14  * &nbsp;&nbsp; data, providing a starting point from which to find an appropriate<br>
15  * <p/>
16  * &nbsp;&nbsp; point to start decoding. The presence of an ASPI frame requires the<br> &nbsp;&nbsp; existence of a TLEN
17  * frame, indicating the duration of the file in<br> &nbsp;&nbsp; milliseconds. There may only be one 'audio seek point
18  * index' frame in<br> &nbsp;&nbsp; a tag.</p>
19  * <p/>
20  * <p>&nbsp;&nbsp;&nbsp;&nbsp; &lt;Header for 'Seek Point Index', ID: &quot;ASPI&quot;&gt;<br> &nbsp;&nbsp;&nbsp;&nbsp;
21  * Indexed data start (S)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $xx xx xx xx<br> &nbsp;&nbsp;&nbsp;&nbsp;
22  * Indexed data length (L)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $xx xx xx xx<br> &nbsp;&nbsp;&nbsp;&nbsp; Number of
23  * index points (N)&nbsp;&nbsp;&nbsp;&nbsp; $xx xx<br>
24  * <p/>
25  * &nbsp;&nbsp;&nbsp;&nbsp; Bits per index point (b)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $xx</p>
26  * <p/>
27  * <p>&nbsp;&nbsp; Then for every index point the following data is included;</p>
28  * <p/>
29  * <p>&nbsp;&nbsp;&nbsp;&nbsp; Fraction at index (Fi)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $xx
30  * (xx)</p>
31  * <p/>
32  * <p>&nbsp;&nbsp; 'Indexed data start' is a byte offset from the beginning of the file.<br> &nbsp;&nbsp; 'Indexed data
33  * length' is the byte length of the audio data being<br> &nbsp;&nbsp; indexed. 'Number of index points' is the number
34  * of index points, as<br> &nbsp;&nbsp; the name implies. The recommended number is 100. 'Bits per index<br>
35  * &nbsp;&nbsp; point' is 8 or 16, depending on the chosen precision. 8 bits works<br>
36  * <p/>
37  * &nbsp;&nbsp; well for short files (less than 5 minutes of audio), while 16 bits is<br> &nbsp;&nbsp; advantageous for
38  * long files. 'Fraction at index' is the numerator of<br> &nbsp;&nbsp; the fraction representing a relative position in
39  * the data. The<br> &nbsp;&nbsp; denominator is 2 to the power of b.</p>
40  * <p/>
41  * <p>&nbsp;&nbsp; Here are the algorithms to be used in the calculation. The known data<br> &nbsp;&nbsp; must be the
42  * offset of the start of the indexed data (S), the offset<br> &nbsp;&nbsp; of the end of the indexed data (E), the
43  * number of index points (N),<br> &nbsp;&nbsp; the offset at index i (Oi). We calculate the fraction at index i<br>
44  * &nbsp;&nbsp; (Fi).</p>
45  * <p/>
46  * <p>&nbsp;&nbsp; Oi is the offset of the frame whose start is soonest after the point<br> &nbsp;&nbsp; for which the
47  * time offset is (i/N * duration).</p>
48  * <p/>
49  * <p>&nbsp;&nbsp; The frame data should be calculated as follows:</p>
50  * <p/>
51  * <p>&nbsp;&nbsp;&nbsp;&nbsp; Fi = Oi/L * 2^b&nbsp;&nbsp;&nbsp; (rounded down to the nearest integer)</p>
52  * <p/>
53  * <p>&nbsp;&nbsp; Offset calculation should be calculated as follows from data in the<br> &nbsp;&nbsp; frame:</p>
54  * <p/>
55  * <p>&nbsp;&nbsp;&nbsp;&nbsp; Oi = (Fi/2^b)*L&nbsp;&nbsp;&nbsp; (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 }