KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > media > format > audio > oggvorbis > VorbisInfo


1 /*
2  * JBoss, the OpenSource J2EE webOS
3  *
4  * Distributable under LGPL license.
5  * See terms of license at gnu.org.
6  */

7
8 package org.jboss.media.format.audio.oggvorbis;
9
10 import java.io.BufferedInputStream JavaDoc;
11 import java.io.FileInputStream JavaDoc;
12 import java.io.IOException JavaDoc;
13 import java.io.InputStream JavaDoc;
14 import java.util.Arrays JavaDoc;
15 import java.util.Hashtable JavaDoc;
16 import java.util.Iterator JavaDoc;
17 import java.util.Set JavaDoc;
18 import java.util.Vector JavaDoc;
19
20 /**
21  * A utility for reading information and comments from the header
22  * packets of an Ogg Vorbis stream.
23  *
24  * Taken from http://taxiway.swapspace.net/~matt/vorbis/VorbisInfo.java
25  * LGPL licensed.
26  *
27  * @author Matthew M. Elder
28  * @version 0.1
29  */

30 class VorbisInfo
31 {
32    // stuff from info header
33
private int channels;
34    private long rate = 0;
35    private long bitrate_upper;
36    private long bitrate_nominal;
37    private long bitrate_lower;
38
39    // stuff from comment header
40
String JavaDoc vendor;
41    int comments = 0;
42    Hashtable JavaDoc comment;
43
44    // for doing checksum
45
private long[] crc_lookup = new long[256];
46    private int crc_ready = 0;
47
48    // for reading data
49
private byte[] header;
50    private byte[] packet;
51
52    /**
53     * Initializes Ogg Vorbis information based on the header from a stream.
54     * @param s an Ogg Vorbis data stream
55     */

56    public VorbisInfo(InputStream JavaDoc s) throws IOException JavaDoc
57    {
58       /* initialize the crc_lookup table */
59       for (int i = 0; i < 256; i++)
60          crc_lookup[i] = _ogg_crc_entry(i);
61
62       fetch_header_and_packet(s);
63       interpret_header_packet();
64
65       fetch_header_and_packet(s);
66       interpret_header_packet();
67    }
68
69    /**
70     * Returns the number of channels in the bitstream.
71     * @return the number of channels in the bitstream.
72     */

73    public int getChannels()
74    {
75       return channels;
76    }
77
78    /**
79     * Returns the rate of the stream in Hz.
80     * @return the rate of the stream in Hz.
81     */

82    public long getRate()
83    {
84       return rate;
85    }
86
87    /**
88     * Returns the <em>average</em> bitrate of the stream in kbps.
89     * @return the <em>average</em> bitrate of the stream in kbps.
90     */

91    public long getBitrate()
92    {
93       return bitrate_nominal;
94    }
95
96    /**
97     * Returns a <code>Vector</code> containing values from the comment header.
98     * Vorbis comments take the form:
99     * <blockquote><tt>FIELD=SOME STRING VALUE.</tt></blockquote>
100     * Since there is no requirement for FIELD to be unique there may
101     * be multiple values for one field.
102     * <code>getComments</code> returns a <code>Vector</code> of
103     * <code>String</code> for all of the strings associated with
104     * a field.
105     * <br>Note: <code>field</code> is case insensitive.<br>
106     *
107     * @param field a case insensitive string for a field name in an Ogg Vorbis
108     * comment header
109     *
110     * @return a Vector of strings containing field values from an Ogg Vorbis
111                comment header
112     */

113    public Vector JavaDoc getComments(String JavaDoc field)
114    {
115       return (Vector JavaDoc) comment.get(field.toLowerCase());
116    }
117
118    /**
119     * Returns a <code>Set</code> of comment field names in no particular order.
120     * @return a <code>Set</code> of comment field names in no particular order.
121     */

122    public Set JavaDoc getFields()
123    {
124       return comment.keySet();
125    }
126
127    private void fetch_header_and_packet(InputStream JavaDoc s) throws IOException JavaDoc
128    {
129       // read in the minimal packet header
130
byte[] head = new byte[27];
131       int bytes = s.read(head);
132       //System.err.println("\nDEBUG: bytes = "+bytes);
133
if (bytes < 27)
134          throw new IOException JavaDoc("Not enough bytes in header");
135
136       if (!"OggS".equals(new String JavaDoc(head, 0, 4)))
137          throw new IOException JavaDoc("Not a valid Ogg Vorbis file");
138
139       int headerbytes = (touint(head[26])) + 27;
140       //System.err.println("DEBUG: headerbytes = "+headerbytes);
141

142       // get the rest of the header
143
byte[] head_rest = new byte[touint(head[26])]; // :-) that's a pun
144
bytes += s.read(head_rest);
145       //System.err.println("DEBUG: bytes = "+bytes);
146
header = new byte[headerbytes];
147       Arrays.fill(header, (byte) 0);
148
149       // copy the whole header into header
150
System.arraycopy(head, 0, header, 0, 27);
151       System.arraycopy(head_rest, 0, header, 27, headerbytes - 27);
152
153       if (bytes < headerbytes)
154       {
155          String JavaDoc error =
156             "Error reading vorbis file: "
157                + "Not enough bytes for header + seg table";
158          throw new IOException JavaDoc(error);
159       }
160
161       int bodybytes = 0;
162       for (int i = 0; i < header[26]; i++)
163          bodybytes += touint(header[27 + i]);
164       //System.err.println("DEBUG: bodybytes = "+bodybytes);
165

166       packet = new byte[bodybytes];
167       Arrays.fill(packet, (byte) 0);
168       bytes += s.read(packet);
169       //System.err.println("DEBUG: bytes = "+bytes);
170

171       if (bytes < headerbytes + bodybytes)
172       {
173          String JavaDoc error =
174             "Error reading vorbis file: "
175                + "Not enough bytes for header + body";
176          throw new IOException JavaDoc(error);
177       }
178
179       byte[] oldsum = new byte[4];
180       System.arraycopy(header, 22, oldsum, 0, 4); // read existing checksum
181
Arrays.fill(header, 22, 22 + 4, (byte) 0);
182       // clear for calculation of checksum
183

184       byte[] newsum = checksum();
185       if (!(new String JavaDoc(oldsum)).equals(new String JavaDoc(newsum)))
186       {
187          System.err.println("checksum failed");
188          System.err.println(
189             "old checksum: "
190                + oldsum[0]
191                + "|"
192                + oldsum[1]
193                + "|"
194                + oldsum[2]
195                + "|"
196                + oldsum[3]);
197          System.err.println(
198             "new checksum: "
199                + newsum[0]
200                + "|"
201                + newsum[1]
202                + "|"
203                + newsum[2]
204                + "|"
205                + newsum[3]);
206       }
207    }
208
209    private void interpret_header_packet() throws IOException JavaDoc
210    {
211       byte packet_type = packet[0];
212       switch (packet_type)
213       {
214          case 1 :
215             //System.err.println("DEBUG: got header packet");
216
if (rate != 0)
217                throw new IOException JavaDoc("Invalid vorbis file: info already fetched");
218             fetch_info_info();
219             break;
220          case 3 :
221             //System.err.println("DEBUG: got comment packet");
222
if (rate == 0)
223                throw new IOException JavaDoc("Invalid vorbis file: header not complete");
224             fetch_comment_info();
225             break;
226          case 5 :
227             throw new IOException JavaDoc("Invalid vorbis file: header not complete");
228          default :
229             throw new IOException JavaDoc("Invalid vorbis file: bad packet header");
230       }
231    }
232
233    /**
234     * pull the fields from the info header
235     */

236    private void fetch_info_info() throws IOException JavaDoc
237    {
238       // keep track of location in packet
239
int dataptr = 1; // should have already read packet[0] for packet type
240

241       String JavaDoc str = new String JavaDoc(packet, dataptr, 6);
242       dataptr += 6;
243       if (!"vorbis".equals(str))
244          throw new IOException JavaDoc("Not a vorbis header");
245       dataptr += 4; // skip version (4 bytes)
246

247       channels = packet[dataptr++]; // 1 byte
248

249       rate = toulong(read32(packet, dataptr));
250       dataptr += 4; // just read 4 bytes
251

252       bitrate_upper = toulong(read32(packet, dataptr));
253       dataptr += 4; // just read 4 bytes
254

255       bitrate_nominal = toulong(read32(packet, dataptr));
256       dataptr += 4; // just read 4 bytes
257

258       bitrate_lower = toulong(read32(packet, dataptr));
259       dataptr += 4; // just read 4 bytes
260

261       dataptr++; // skip block sizes (4 bits each for a total of 1 byte)
262

263       byte eop = packet[dataptr++];
264       if (eop != 1)
265          throw new IOException JavaDoc("End of packet expected but not found");
266    }
267
268    private void fetch_comment_info() throws IOException JavaDoc
269    {
270       int dataptr = 1;
271
272       String JavaDoc str = new String JavaDoc(packet, dataptr, 6);
273       dataptr += 6;
274       if (!"vorbis".equals(str))
275          throw new IOException JavaDoc("Not a vorbis header");
276
277       comment = new Hashtable JavaDoc();
278
279       long len = toulong(read32(packet, dataptr));
280       //System.err.println("DEBUG: vendor string length = "+len);
281
dataptr += 4;
282
283       /*
284        * FIXME: Casting len to int here means possible loss of data.
285        * I don't know who would have a comment header big
286        * enough to cause this, but the spec says this is
287        * an unsigned 32 bit int.
288        * Damn java for not having unsigned ints (or it should
289        * at least allow the use of 64 bit longs more frequently)
290        * If I get a chance i'll write a wrapper for this(maybe).
291        */

292       vendor = new String JavaDoc(packet, dataptr, (int) len);
293       dataptr += len;
294
295       // FIXME: similar problem to the vendor string length above
296
comments = (int) toulong(read32(packet, dataptr));
297       dataptr += 4;
298
299       for (int i = 0; i < comments; i++)
300       {
301
302          // read comment
303
len = toulong(read32(packet, dataptr));
304          dataptr += 4;
305          // FIXME: same problem as vendor string
306
String JavaDoc cmnt = new String JavaDoc(packet, dataptr, (int) len);
307          dataptr += len;
308
309          // parse and store
310
String JavaDoc name = cmnt.substring(0, cmnt.indexOf('='));
311          String JavaDoc value = cmnt.substring(cmnt.indexOf('=') + 1);
312          if (comment.containsKey(name))
313          {
314             Vector JavaDoc tmp = (Vector JavaDoc) comment.get(name.toLowerCase());
315             tmp.add(value);
316          }
317          else
318          {
319             Vector JavaDoc tmp = new Vector JavaDoc();
320             tmp.add(value);
321             comment.put(name.toLowerCase(), tmp);
322          }
323       }
324    }
325
326    private int read32(byte[] data, int ptr)
327    {
328       int val = 0;
329       val = (touint(data[ptr]) & 0x000000ff);
330       val |= ((touint(data[ptr + 1]) << 8) & 0x0000ff00);
331       val |= ((touint(data[ptr + 2]) << 16) & 0x00ff0000);
332       val |= ((touint(data[ptr + 3]) << 24) & 0xff000000);
333       return val;
334    }
335
336    private byte[] checksum()
337    {
338       long crc_reg = 0;
339
340       for (int i = 0; i < header.length; i++)
341       {
342          int tmp = (int) (((crc_reg >>> 24) & 0xff) ^ touint(header[i]));
343          crc_reg = (crc_reg << 8) ^ crc_lookup[tmp];
344          crc_reg &= 0xffffffff;
345       }
346       for (int i = 0; i < packet.length; i++)
347       {
348          int tmp = (int) (((crc_reg >>> 24) & 0xff) ^ touint(packet[i]));
349          crc_reg = (crc_reg << 8) ^ crc_lookup[tmp];
350          crc_reg &= 0xffffffff;
351       }
352
353       byte[] sum = new byte[4];
354       sum[0] = (byte) (crc_reg & 0xffL);
355       sum[1] = (byte) ((crc_reg >>> 8) & 0xffL);
356       sum[2] = (byte) ((crc_reg >>> 16) & 0xffL);
357       sum[3] = (byte) ((crc_reg >>> 24) & 0xffL);
358
359       return sum;
360    }
361
362    private long _ogg_crc_entry(long index)
363    {
364       long r;
365
366       r = index << 24;
367       for (int i = 0; i < 8; i++)
368       {
369          if ((r & 0x80000000L) != 0)
370          {
371             r = (r << 1) ^ 0x04c11db7L;
372          }
373          else
374          {
375             r <<= 1;
376          }
377       }
378       return (r & 0xffffffff);
379    }
380
381    private long toulong(int n)
382    {
383       return (n & 0xffffffffL);
384    }
385
386    private int touint(byte n)
387    {
388       return (n & 0xff);
389    }
390
391    /**
392     * Prints out the information from an Ogg Vorbis stream in a
393     * nice, humanly-readable format.
394     */

395    public String JavaDoc toString()
396    {
397
398       String JavaDoc str = "";
399
400       str += channels + " channels at " + rate + "Hz\n";
401       str += bitrate_nominal / 1000 + "kbps (average bitrate)\n";
402
403       Iterator JavaDoc fields = comment.keySet().iterator();
404       while (fields.hasNext())
405       {
406          String JavaDoc name = (String JavaDoc) fields.next();
407          Vector JavaDoc values = (Vector JavaDoc) comment.get(name);
408          Iterator JavaDoc vi = values.iterator();
409          str += name + "=";
410          boolean dumb = false;
411          while (vi.hasNext())
412          {
413             if (dumb)
414                str += ", ";
415             str += vi.next();
416             dumb = true;
417          }
418          str += "\n";
419       }
420
421       return str;
422    }
423
424    //tester main
425
static void main(String JavaDoc[] args) throws Exception JavaDoc
426    {
427       if (args.length != 1)
428       {
429          System.err.println("usage:\tjava VorbisInfo <ogg vorbis file>");
430          System.exit(1);
431       }
432       BufferedInputStream JavaDoc b =
433          new BufferedInputStream JavaDoc(new FileInputStream JavaDoc(args[0]));
434       VorbisInfo vi = new VorbisInfo(b);
435       System.out.println("\n" + vi);
436
437       b.close();
438    }
439    
440    // Added by Ricardo Argüello
441
public Hashtable JavaDoc getComments() {
442       return comment;
443    }
444 }
Popular Tags