summaryrefslogtreecommitdiff
path: root/songdbj/de/jarnbjo/ogg/OggPage.java
diff options
context:
space:
mode:
Diffstat (limited to 'songdbj/de/jarnbjo/ogg/OggPage.java')
-rw-r--r--songdbj/de/jarnbjo/ogg/OggPage.java431
1 files changed, 431 insertions, 0 deletions
diff --git a/songdbj/de/jarnbjo/ogg/OggPage.java b/songdbj/de/jarnbjo/ogg/OggPage.java
new file mode 100644
index 0000000000..cc965cc7a9
--- /dev/null
+++ b/songdbj/de/jarnbjo/ogg/OggPage.java
@@ -0,0 +1,431 @@
1/*
2 * $ProjectName$
3 * $ProjectRevision$
4 * -----------------------------------------------------------
5 * $Id$
6 * -----------------------------------------------------------
7 *
8 * $Author$
9 *
10 * Description:
11 *
12 * Copyright 2002-2003 Tor-Einar Jarnbjo
13 * -----------------------------------------------------------
14 *
15 * Change History
16 * -----------------------------------------------------------
17 * $Log$
18 * Revision 1.1 2005/07/11 15:42:36 hcl
19 * Songdb java version, source. only 1.5 compatible
20 *
21 * Revision 1.1.1.1 2004/04/04 22:09:12 shred
22 * First Import
23 *
24 * Revision 1.3 2003/04/10 19:48:22 jarnbjo
25 * no message
26 *
27 * Revision 1.2 2003/03/31 00:23:04 jarnbjo
28 * no message
29 *
30 * Revision 1.1 2003/03/03 21:02:20 jarnbjo
31 * no message
32 *
33 */
34
35package de.jarnbjo.ogg;
36
37import java.io.*;
38
39import de.jarnbjo.util.io.*;
40
41/**
42 * <p>An instance of this class represents an ogg page read from an ogg file
43 * or network stream. It has no public constructor, but instances can be
44 * created by the <code>create</code> methods, supplying a JMF stream or
45 * a <code>RandomAccessFile</code>
46 * which is positioned at the beginning of an Ogg page.</p>
47 *
48 * <p>Furtheron, the class provides methods for accessing the raw page data,
49 * as well as data attributes like segmenting information, sequence number,
50 * stream serial number, chechsum and wether this page is the beginning or
51 * end of a logical bitstream (BOS, EOS) and if the page data starts with a
52 * continued packet or a fresh data packet.</p>
53 */
54
55public class OggPage {
56
57 private int version;
58 private boolean continued, bos, eos;
59 private long absoluteGranulePosition;
60 private int streamSerialNumber, pageSequenceNumber, pageCheckSum;
61 private int[] segmentOffsets;
62 private int[] segmentLengths;
63 private int totalLength;
64 private byte[] header, segmentTable, data;
65
66 protected OggPage() {
67 }
68
69 private OggPage(
70 int version,
71 boolean continued,
72 boolean bos,
73 boolean eos,
74 long absoluteGranulePosition,
75 int streamSerialNumber,
76 int pageSequenceNumber,
77 int pageCheckSum,
78 int[] segmentOffsets,
79 int[] segmentLengths,
80 int totalLength,
81 byte[] header,
82 byte[] segmentTable,
83 byte[] data) {
84
85 this.version=version;
86 this.continued=continued;
87 this.bos=bos;
88 this.eos=eos;
89 this.absoluteGranulePosition=absoluteGranulePosition;
90 this.streamSerialNumber=streamSerialNumber;
91 this.pageSequenceNumber=pageSequenceNumber;
92 this.pageCheckSum=pageCheckSum;
93 this.segmentOffsets=segmentOffsets;
94 this.segmentLengths=segmentLengths;
95 this.totalLength=totalLength;
96 this.header=header;
97 this.segmentTable=segmentTable;
98 this.data=data;
99 }
100
101 /**
102 * this method equals to create(RandomAccessFile source, false)
103 *
104 * @see #create(RandomAccessFile, boolean)
105 */
106
107 public static OggPage create(RandomAccessFile source) throws IOException, EndOfOggStreamException, OggFormatException {
108 return create(source, false);
109 }
110
111 /**
112 * This method is called to read data from the current position in the
113 * specified RandomAccessFile and create a new OggPage instance based on the data
114 * read. If the parameter <code>skipData</code> is set to <code>true</code>,
115 * the actual page segments (page data) is skipped and not read into
116 * memory. This mode is useful when scanning through an ogg file to build
117 * a seek table.
118 *
119 * @param source the source from which the ogg page is generated
120 * @param skipData if set to <code>true</code>, the actual page data is not read into memory
121 * @return an ogg page created by reading data from the specified source, starting at the current position
122 * @throws FormatException if the data read from the specified source is not matching the specification for an ogg page
123 * @throws EndOfStreamException if it is not possible to read an entire ogg page from the specified source
124 * @throws IOException if some other I/O error is detected when reading from the source
125 *
126 * @see #create(RandomAccessFile)
127 */
128
129 public static OggPage create(RandomAccessFile source, boolean skipData) throws IOException, EndOfOggStreamException, OggFormatException {
130 return create((Object)source, skipData);
131 }
132
133 /**
134 * this method equals to create(InputStream source, false)
135 *
136 * @see #create(InputStream, boolean)
137 */
138
139 public static OggPage create(InputStream source) throws IOException, EndOfOggStreamException, OggFormatException {
140 return create(source, false);
141 }
142
143 /**
144 * This method is called to read data from the current position in the
145 * specified InpuStream and create a new OggPage instance based on the data
146 * read. If the parameter <code>skipData</code> is set to <code>true</code>,
147 * the actual page segments (page data) is skipped and not read into
148 * memory. This mode is useful when scanning through an ogg file to build
149 * a seek table.
150 *
151 * @param source the source from which the ogg page is generated
152 * @param skipData if set to <code>true</code>, the actual page data is not read into memory
153 * @return an ogg page created by reading data from the specified source, starting at the current position
154 * @throws FormatException if the data read from the specified source is not matching the specification for an ogg page
155 * @throws EndOfStreamException if it is not possible to read an entire ogg page from the specified source
156 * @throws IOException if some other I/O error is detected when reading from the source
157 *
158 * @see #create(InputStream)
159 */
160
161 public static OggPage create(InputStream source, boolean skipData) throws IOException, EndOfOggStreamException, OggFormatException {
162 return create((Object)source, skipData);
163 }
164
165 /**
166 * this method equals to create(byte[] source, false)
167 *
168 * @see #create(byte[], boolean)
169 */
170
171 public static OggPage create(byte[] source) throws IOException, EndOfOggStreamException, OggFormatException {
172 return create(source, false);
173 }
174
175 /**
176 * This method is called to
177 * create a new OggPage instance based on the specified byte array.
178 *
179 * @param source the source from which the ogg page is generated
180 * @param skipData if set to <code>true</code>, the actual page data is not read into memory
181 * @return an ogg page created by reading data from the specified source, starting at the current position
182 * @throws FormatException if the data read from the specified source is not matching the specification for an ogg page
183 * @throws EndOfStreamException if it is not possible to read an entire ogg page from the specified source
184 * @throws IOException if some other I/O error is detected when reading from the source
185 *
186 * @see #create(byte[])
187 */
188
189 public static OggPage create(byte[] source, boolean skipData) throws IOException, EndOfOggStreamException, OggFormatException {
190 return create((Object)source, skipData);
191 }
192
193 private static OggPage create(Object source, boolean skipData) throws IOException, EndOfOggStreamException, OggFormatException {
194
195 try {
196 int sourceOffset=27;
197
198 byte[] header=new byte[27];
199 if(source instanceof RandomAccessFile) {
200 RandomAccessFile raf=(RandomAccessFile)source;
201 if(raf.getFilePointer()==raf.length()) {
202 return null;
203 }
204 raf.readFully(header);
205 }
206 else if(source instanceof InputStream) {
207 readFully((InputStream)source, header);
208 }
209 else if(source instanceof byte[]) {
210 System.arraycopy((byte[])source, 0, header, 0, 27);
211 }
212
213 BitInputStream bdSource=new ByteArrayBitInputStream(header);
214
215 int capture=bdSource.getInt(32);
216
217 if(capture!=0x5367674f) {
218 //throw new FormatException("Ogg page does not start with 'OggS' (0x4f676753)");
219
220 /*
221 ** This condition is IMHO an error, but older Ogg files often contain
222 ** pages with a different capture than OggS. I am not sure how to
223 ** manage these pages, but the decoder seems to work properly, if
224 ** the incorrect capture is simply ignored.
225 */
226
227 String cs=Integer.toHexString(capture);
228 while(cs.length()<8) {
229 cs="0"+cs;
230 }
231 cs=cs.substring(6, 8)+cs.substring(4, 6)+cs.substring(2, 4)+cs.substring(0, 2);
232 char c1=(char)(Integer.valueOf(cs.substring(0, 2), 16).intValue());
233 char c2=(char)(Integer.valueOf(cs.substring(2, 4), 16).intValue());
234 char c3=(char)(Integer.valueOf(cs.substring(4, 6), 16).intValue());
235 char c4=(char)(Integer.valueOf(cs.substring(6, 8), 16).intValue());
236 System.out.println("Ogg packet header is 0x"+cs+" ("+c1+c2+c3+c4+"), should be 0x4f676753 (OggS)");
237 }
238
239 int version=bdSource.getInt(8);
240 byte tmp=(byte)bdSource.getInt(8);
241 boolean bf1=(tmp&1)!=0;
242 boolean bos=(tmp&2)!=0;
243 boolean eos=(tmp&4)!=0;
244 long absoluteGranulePosition=bdSource.getLong(64);
245 int streamSerialNumber=bdSource.getInt(32);
246 int pageSequenceNumber=bdSource.getInt(32);
247 int pageCheckSum=bdSource.getInt(32);
248 int pageSegments=bdSource.getInt(8);
249
250 //System.out.println("OggPage: "+streamSerialNumber+" / "+absoluteGranulePosition+" / "+pageSequenceNumber);
251
252 int[] segmentOffsets=new int[pageSegments];
253 int[] segmentLengths=new int[pageSegments];
254 int totalLength=0;
255
256 byte[] segmentTable=new byte[pageSegments];
257 byte[] tmpBuf=new byte[1];
258
259 for(int i=0; i<pageSegments; i++) {
260 int l=0;
261 if(source instanceof RandomAccessFile) {
262 l=((int)((RandomAccessFile)source).readByte()&0xff);
263 }
264 else if(source instanceof InputStream) {
265 l=(int)((InputStream)source).read();
266 }
267 else if(source instanceof byte[]) {
268 l=(int)((byte[])source)[sourceOffset++];
269 l&=255;
270 }
271 segmentTable[i]=(byte)l;
272 segmentLengths[i]=l;
273 segmentOffsets[i]=totalLength;
274 totalLength+=l;
275 }
276
277 byte[] data=null;
278
279 if(!skipData) {
280
281 //System.out.println("createPage: "+absoluteGranulePosition*1000/44100);
282
283 data=new byte[totalLength];
284 //source.read(data, 0, totalLength);
285 if(source instanceof RandomAccessFile) {
286 ((RandomAccessFile)source).readFully(data);
287 }
288 else if(source instanceof InputStream) {
289 readFully((InputStream)source, data);
290 }
291 else if(source instanceof byte[]) {
292 System.arraycopy(source, sourceOffset, data, 0, totalLength);
293 }
294 }
295
296 return new OggPage(version, bf1, bos, eos, absoluteGranulePosition, streamSerialNumber, pageSequenceNumber, pageCheckSum, segmentOffsets, segmentLengths, totalLength, header, segmentTable, data);
297 }
298 catch(EOFException e) {
299 throw new EndOfOggStreamException();
300 }
301 }
302
303 private static void readFully(InputStream source, byte[] buffer) throws IOException {
304 int total=0;
305 while(total<buffer.length) {
306 int read=source.read(buffer, total, buffer.length-total);
307 if(read==-1) {
308 throw new EndOfOggStreamException();
309 }
310 total+=read;
311 }
312 }
313
314 /**
315 * Returns the absolute granule position of the last complete
316 * packet contained in this Ogg page, or -1 if the page contains a single
317 * packet, which is not completed on this page. For pages containing Vorbis
318 * data, this value is the sample index within the Vorbis stream. The Vorbis
319 * stream does not necessarily start with sample index 0.
320 *
321 * @return the absolute granule position of the last packet completed on
322 * this page
323 */
324
325
326 public long getAbsoluteGranulePosition() {
327 return absoluteGranulePosition;
328 }
329
330 /**
331 * Returns the stream serial number of this ogg page.
332 *
333 * @return this page's serial number
334 */
335
336 public int getStreamSerialNumber() {
337 return streamSerialNumber;
338 }
339
340 /**
341 * Return the sequnce number of this ogg page.
342 *
343 * @return this page's sequence number
344 */
345
346 public int getPageSequenceNumber() {
347 return pageSequenceNumber;
348 }
349
350 /**
351 * Return the check sum of this ogg page.
352 *
353 * @return this page's check sum
354 */
355
356 public int getPageCheckSum() {
357 return pageCheckSum;
358 }
359
360 /**
361 * @return the total number of bytes in the page data
362 */
363
364
365 public int getTotalLength() {
366 if(data!=null) {
367 return 27+segmentTable.length+data.length;
368 }
369 else {
370 return totalLength;
371 }
372 }
373
374 /**
375 * @return a ByteBuffer containing the page data
376 */
377
378
379 public byte[] getData() {
380 return data;
381 }
382
383 public byte[] getHeader() {
384 return header;
385 }
386
387 public byte[] getSegmentTable() {
388 return segmentTable;
389 }
390
391 public int[] getSegmentOffsets() {
392 return segmentOffsets;
393 }
394
395 public int[] getSegmentLengths() {
396 return segmentLengths;
397 }
398
399 /**
400 * @return <code>true</code> if this page begins with a continued packet
401 */
402
403 public boolean isContinued() {
404 return continued;
405 }
406
407 /**
408 * @return <code>true</code> if this page begins with a fresh packet
409 */
410
411 public boolean isFresh() {
412 return !continued;
413 }
414
415 /**
416 * @return <code>true</code> if this page is the beginning of a logical stream
417 */
418
419 public boolean isBos() {
420 return bos;
421 }
422
423 /**
424 * @return <code>true</code> if this page is the end of a logical stream
425 */
426
427 public boolean isEos() {
428 return eos;
429 }
430
431} \ No newline at end of file