1 package org.xnap.plugin.viewer.vorbisviewer;
2
3 import java.io.BufferedInputStream;
4 import java.io.FileInputStream;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.util.Arrays;
8 import java.util.Hashtable;
9 import java.util.Iterator;
10 import java.util.Set;
11 import java.util.Vector;
12
13 /***
14 *
15 * A utility for reading information and comments from the header
16 * packets of an Ogg Vorbis stream.
17 *
18 * <tt>
19 * <p>
20 * Copyright (C) 2001 Matthew Elder
21 * </p>
22 * <p>
23 * This library is free software; you can redistribute it and/or
24 * modify it under the terms of the GNU Library General Public
25 * License as published by the Free Software Foundation; either
26 * version 2 of the License, or (at your option) any later version.
27 * </p>
28 * <p>
29 * This library is distributed in the hope that it will be useful,
30 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 * GNU Library General Public License for more details.
33 * </p>
34 * <p>
35 * You should have received a copy of the GNU Library General Public
36 * License along with this library; if not, write to the
37 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
38 * Boston, MA 02111-1307, USA.
39 * </p>
40 * </tt>
41 *
42 * @author Matthew M. Elder
43 * @version 0.1
44 *
45 */
46
47 public class VorbisInfo {
48
49
50 private int channels;
51 private long rate=0;
52 private long bitrate_upper;
53 private long bitrate_nominal;
54 private long bitrate_lower;
55
56
57 String vendor;
58 int comments=0;
59 Hashtable comment;
60
61
62 private long[] crc_lookup = new long[256];
63 private int crc_ready=0;
64
65
66 private byte[] header;
67 private byte[] packet;
68
69 /***
70 * Initializes Ogg Vorbis information based on the header from a stream.
71 * @param s an Ogg Vorbis data stream
72 */
73 public VorbisInfo(InputStream s) throws IOException {
74
75
76 for (int i=0;i<256;i++)
77 crc_lookup[i]=_ogg_crc_entry(i);
78
79 fetch_header_and_packet(s);
80 interpret_header_packet();
81
82 fetch_header_and_packet(s);
83 interpret_header_packet();
84 }
85
86 /***
87 * Returns the number of channels in the bitstream.
88 * @return the number of channels in the bitstream.
89 */
90 public int getChannels() {
91 return channels;
92 }
93
94 /***
95 * Returns the rate of the stream in Hz.
96 * @return the rate of the stream in Hz.
97 */
98 public long getRate() {
99 return rate;
100 }
101
102 /***
103 * Returns the <em>average</em> bitrate of the stream in kbps.
104 * @return the <em>average</em> bitrate of the stream in kbps.
105 */
106 public long getBitrate() {
107 return bitrate_nominal;
108 }
109
110 /***
111 * Returns a <code>Vector</code> containing values from the comment header.
112 * Vorbis comments take the form:
113 * <blockquote><tt>FIELD=SOME STRING VALUE.</tt></blockquote>
114 * Since there is no requirement for FIELD to be unique there may
115 * be multiple values for one field.
116 * <code>getComments</code> returns a <code>Vector</code> of
117 * <code>String</code> for all of the strings associated with
118 * a field.
119 * <br>Note: <code>field</code> is case insensitive.<br>
120 *
121 * @param field a case insensitive string for a field name in an Ogg Vorbis
122 * comment header
123 *
124 * @return a Vector of strings containing field values from an Ogg Vorbis
125 comment header
126 */
127 public Vector getComments(String field) {
128 return (Vector)comment.get(field.toLowerCase());
129 }
130
131 /***
132 * Returns a <code>Set</code> of comment field names in no particular order.
133 * @return a <code>Set</code> of comment field names in no particular order.
134 */
135 public Set getFields() {
136 return comment.keySet();
137 }
138
139 private void fetch_header_and_packet(InputStream s) throws IOException {
140
141
142 byte[] head = new byte[27];
143 int bytes = s.read(head);
144
145 if(bytes < 27)
146 throw new IOException("Not enough bytes in header");
147
148 if(!"OggS".equals(new String(head, 0, 4)))
149 throw new IOException("Not a valid Ogg Vorbis file");
150
151 int headerbytes = (touint(head[26])) + 27;
152
153
154
155 byte[] head_rest = new byte[touint(head[26])];
156 bytes += s.read(head_rest);
157
158 header = new byte[headerbytes];
159 Arrays.fill(header,(byte)0);
160
161
162 System.arraycopy(head,0,header,0,27);
163 System.arraycopy(head_rest,0,header,27,headerbytes-27);
164
165 if(bytes<headerbytes) {
166 String error =
167 "Error reading vorbis file: " +
168 "Not enough bytes for header + seg table";
169 throw new IOException(error);
170 }
171
172 int bodybytes = 0;
173 for(int i=0; i<header[26]; i++)
174 bodybytes += touint(header[27+i]);
175
176
177 packet = new byte[bodybytes];
178 Arrays.fill(packet,(byte)0);
179 bytes += s.read(packet);
180
181
182 if(bytes<headerbytes+bodybytes) {
183 String error =
184 "Error reading vorbis file: " +
185 "Not enough bytes for header + body";
186 throw new IOException(error);
187 }
188
189 byte[] oldsum = new byte[4];
190 System.arraycopy(header,22,oldsum,0,4);
191 Arrays.fill(header, 22, 22+4, (byte)0);
192
193 byte[] newsum = checksum();
194 if(!(new String(oldsum)).equals(new String(newsum)) ) {
195 System.err.println("checksum failed");
196 System.err.println("old checksum: " +
197 oldsum[0]+"|"+oldsum[1]+"|"+oldsum[2]+"|"+oldsum[3]);
198 System.err.println("new checksum: " +
199 newsum[0]+"|"+newsum[1]+"|"+newsum[2]+"|"+newsum[3]);
200 }
201
202 }
203
204 private void interpret_header_packet() throws IOException {
205 byte packet_type = packet[0];
206 switch(packet_type) {
207 case 1:
208
209 if(rate != 0)
210 throw new IOException("Invalid vorbis file: info already fetched");
211 fetch_info_info();
212 break;
213 case 3:
214
215 if(rate == 0)
216 throw new IOException("Invalid vorbis file: header not complete");
217 fetch_comment_info();
218 break;
219 case 5:
220 throw new IOException("Invalid vorbis file: header not complete");
221 default:
222 throw new IOException("Invalid vorbis file: bad packet header");
223 }
224 }
225
226 /***
227 * pull the fields from the info header
228 */
229 private void fetch_info_info() throws IOException {
230
231
232 int dataptr=1;
233
234 String str = new String(packet,dataptr,6);
235 dataptr += 6;
236 if(!"vorbis".equals(str))
237 throw new IOException("Not a vorbis header");
238 dataptr += 4;
239
240 channels = packet[dataptr++];
241
242 rate = toulong(read32(packet, dataptr));
243 dataptr += 4;
244
245 bitrate_upper = toulong(read32(packet, dataptr));
246 dataptr += 4;
247
248 bitrate_nominal = toulong(read32(packet, dataptr));
249 dataptr += 4;
250
251 bitrate_lower = toulong(read32(packet, dataptr));
252 dataptr += 4;
253
254 dataptr++;
255
256 byte eop = packet[dataptr++];
257 if(eop!=1) throw new IOException("End of packet expected but not found");
258 }
259
260 private void fetch_comment_info() throws IOException {
261 int dataptr=1;
262
263 String str = new String(packet,dataptr,6);
264 dataptr += 6;
265 if(!"vorbis".equals(str))
266 throw new IOException("Not a vorbis header");
267
268 comment = new Hashtable();
269
270 long len = toulong(read32(packet,dataptr));
271
272 dataptr += 4;
273
274
275
276
277
278
279
280
281
282
283 vendor = new String(packet,dataptr,(int)len);
284 dataptr += len;
285
286
287 comments = (int)toulong(read32(packet,dataptr));
288 dataptr += 4;
289
290 for(int i=0; i<comments; i++) {
291
292
293 len = toulong(read32(packet,dataptr));
294 dataptr += 4;
295
296 String cmnt = new String(packet,dataptr,(int)len);
297 dataptr += len;
298
299
300 String name = cmnt.substring(0,cmnt.indexOf('='));
301 String value = cmnt.substring(cmnt.indexOf('=')+1);
302 if(comment.containsKey(name)) {
303 Vector tmp = (Vector)comment.get(name.toLowerCase());
304 tmp.add(value);
305 } else {
306 Vector tmp = new Vector();
307 tmp.add(value);
308 comment.put(name.toLowerCase(),tmp);
309 }
310 }
311 }
312
313 private int read32 (byte[] data, int ptr) {
314 int val = 0;
315 val = ( touint(data[ptr]) & 0x000000ff);
316 val |= ((touint(data[ptr+1]) << 8) & 0x0000ff00);
317 val |= ((touint(data[ptr+2]) << 16) & 0x00ff0000);
318 val |= ((touint(data[ptr+3]) << 24) & 0xff000000);
319 return val;
320 }
321
322 private byte[] checksum() {
323 long crc_reg=0;
324
325 for(int i=0;i<header.length;i++) {
326 int tmp = (int)(((crc_reg >>> 24)&0xff) ^ touint(header[i]));
327 crc_reg=(crc_reg<<8)^crc_lookup[tmp];
328 crc_reg &= 0xffffffff;
329 }
330 for(int i=0;i<packet.length;i++) {
331 int tmp = (int)(((crc_reg >>> 24)&0xff) ^ touint(packet[i]));
332 crc_reg=(crc_reg<<8)^crc_lookup[tmp];
333 crc_reg &= 0xffffffff;
334 }
335
336 byte[] sum = new byte[4];
337 sum[0]=(byte)(crc_reg & 0xffL);
338 sum[1]=(byte)((crc_reg>>>8) & 0xffL);
339 sum[2]=(byte)((crc_reg>>>16) & 0xffL);
340 sum[3]=(byte)((crc_reg>>>24) & 0xffL);
341
342 return sum;
343 }
344
345 private long _ogg_crc_entry(long index){
346 long r;
347
348 r = index << 24;
349 for (int i=0; i<8; i++) {
350 if ((r & 0x80000000L) != 0) {
351 r = (r << 1) ^ 0x04c11db7L;
352 } else {
353 r<<=1;
354 }
355 }
356 return (r & 0xffffffff);
357 }
358
359 private long toulong(int n) {
360 return (n & 0xffffffffL);
361 }
362
363 private int touint(byte n) {
364 return (n & 0xff);
365 }
366
367 /***
368 * Prints out the information from an Ogg Vorbis stream in a
369 * nice, humanly-readable format.
370 */
371 public String toString() {
372
373 String str = "";
374
375 str += channels+" channels at "+rate+"Hz\n";
376 str += bitrate_nominal/1000+"kbps (average bitrate)\n";
377
378 Iterator fields = comment.keySet().iterator();
379 while(fields.hasNext()) {
380 String name = (String)fields.next();
381 Vector values = (Vector)comment.get(name);
382 Iterator vi = values.iterator();
383 str += name+"=";
384 boolean dumb=false;
385 while(vi.hasNext()) {
386 if(dumb) str += ", ";
387 str += vi.next();
388 dumb=true;
389 }
390 str+="\n";
391 }
392
393 return str;
394 }
395
396
397 static void main(String[] args) throws Exception {
398 if(args.length!=1) {
399 System.err.println("usage:\tjava VorbisInfo <ogg vorbis file>");
400 System.exit(1);
401 }
402 BufferedInputStream b =
403 new BufferedInputStream(new FileInputStream(args[0]));
404 VorbisInfo vi = new VorbisInfo(b);
405 System.out.println("\n"+vi);
406
407 b.close();
408 }
409
410 }