001/* 002 * $Source: /cvsroot2/open/projects/jOggPlayer/ca/bc/webarts/widgets/VorbisInfo.java,v $ 003 * $Name: $ 004 * $Revision: 491 $ 005 * $Date: 2008-10-13 18:01:39 -0700 (Mon, 13 Oct 2008) $ 006 * $Locker: $ 007 */ 008/** 009 * A utility for reading information and comments from the header packets of an 010 * Ogg Vorbis stream. <tt> <p> 011 * 012 * Copyright (C) 2001 Matthew Elder </p> <p> 013 * 014 * This library is free software; you can redistribute it and/or modify it 015 * under the terms of the GNU Library General Public License as published by 016 * the Free Software Foundation; either version 2 of the License, or (at your 017 * option) any later version. </p> <p> 018 * 019 * This library is distributed in the hope that it will be useful, but WITHOUT 020 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 021 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License 022 * for more details. </p> <p> 023 * 024 * You should have received a copy of the GNU Library General Public License 025 * along with this library; if not, write to the Free Software Foundation, 026 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. </p> </tt> 027 * 028 * @author Matthew M. Elder 029 * @version 0.1 030 */ 031 032package ca.bc.webarts.widgets; 033 034import java.io.*; 035import java.util.*; 036 037 038/** 039 * This is a class to encapsulate the Vorbis comments associated in a Ogg Vorbis file. 040 * 041 * @author Matthew Elder 042 */ 043public class VorbisInfo 044{ 045 046 // stuff from comment header 047 /** Description of the Field */ 048 String vendor; 049 /** The number of comments. */ 050 int comments = 0; 051 /** The comments name value pairs in a Hashtable. */ 052 Hashtable comment; 053 054 // stuff from info header 055 /** Info Header channels. */ 056 private int channels; 057 /** Info Header bit rate. */ 058 private long rate = 0; 059 /** Description of the Field */ 060 private long bitrate_upper; 061 /** Description of the Field */ 062 private long bitrate_nominal; 063 /** Description of the Field */ 064 private long bitrate_lower; 065 066 // for doing checksum 067 /** for doing checksum. */ 068 private long[] crc_lookup = new long[256]; 069 /** Description of the Field */ 070 private int crc_ready = 0; 071 072 // for reading data 073 /** Description of the Field */ 074 private byte[] header; 075 /** Description of the Field */ 076 private byte[] packet; 077 078 079 /** 080 * Initializes Ogg Vorbis information based on the header from a stream. 081 * 082 * @param s an Ogg Vorbis data stream 083 * @exception IOException Description of the Exception 084 */ 085 public VorbisInfo( InputStream s ) 086 throws IOException 087 { 088 089 /* 090 * initialize the crc_lookup table 091 */ 092 for ( int i = 0; i < 256; i++ ) 093 { 094 crc_lookup[i] = _ogg_crc_entry( i ); 095 } 096 097 fetch_header_and_packet( s ); 098 interpret_header_packet(); 099 100 fetch_header_and_packet( s ); 101 interpret_header_packet(); 102 } 103 104 //tester main 105 /** 106 * The main program for the VorbisInfo class 107 * 108 * @param args The command line arguments 109 * @exception Exception Description of the Exception 110 */ 111 public static void main( String[] args ) 112 throws Exception 113 { 114 if ( args.length != 1 ) 115 { 116 System.err.println( "usage:\tjava VorbisInfo <ogg vorbis file>" ); 117 System.exit( 1 ); 118 } 119 BufferedInputStream b = 120 new BufferedInputStream( new FileInputStream( args[0] ) ); 121 VorbisInfo vi = new VorbisInfo( b ); 122 System.out.println( "\n" + vi ); 123 124 b.close(); 125 } 126 127 128 /** 129 * Returns the number of channels in the bitstream. 130 * 131 * @return the number of channels in the bitstream. 132 */ 133 public int getChannels() 134 { 135 return channels; 136 } 137 138 139 /** 140 * Returns the rate of the stream in Hz. 141 * 142 * @return the rate of the stream in Hz. 143 */ 144 public long getRate() 145 { 146 return rate; 147 } 148 149 150 /** 151 * Returns the <em>average</em> bitrate of the stream in kbps. 152 * 153 * @return the <em>average</em> bitrate of the stream in kbps. 154 */ 155 public long getBitrate() 156 { 157 return bitrate_nominal; 158 } 159 160 161 /** 162 * Returns a <code>Vector</code> containing values from the comment header. 163 * Vorbis comments take the form: <blockquote><tt>FIELD=SOME STRING VALUE. 164 * </tt></blockquote> Since there is no requirement for FIELD to be unique 165 * there may be multiple values for one field. <code>getComments</code> 166 * returns a <code>Vector</code> of <code>String</code> for all of the 167 * strings associated with a field. <br> 168 * Note: <code>field</code> is case insensitive.<br> 169 * 170 * 171 * @param field a case insensitive string for a field name in an Ogg Vorbis 172 * comment header 173 * @return a Vector of strings containing field values from an Ogg 174 * Vorbis comment header 175 */ 176 public Vector getComments( String field ) 177 { 178 Vector retVal = null; 179 if (field != null && comment !=null) 180 { 181 retVal = (Vector)comment.get( field.toLowerCase() ); 182 } 183 return retVal; 184 } 185 186 187 /** 188 * Returns a <code>Set</code> of comment field names in no particular order. 189 * 190 * @return a <code>Set</code> of comment field names in no particular 191 * order. 192 */ 193 public Set getFields() 194 { 195 return comment.keySet(); 196 } 197 198 199 /** 200 * Prints out the information from an Ogg Vorbis stream in a nice, 201 * humanly-readable format. 202 * 203 * @return A string representation of the object. 204 */ 205 public String toString() 206 { 207 208 String str = ""; 209 210 str += channels + " channels at " + rate + "Hz\n"; 211 str += bitrate_nominal / 1000 + "kbps (average bitrate)\n"; 212 213 Iterator fields = comment.keySet().iterator(); 214 while ( fields.hasNext() ) 215 { 216 String name = (String)fields.next(); 217 Vector values = (Vector)comment.get( name ); 218 Iterator vi = values.iterator(); 219 str += name + "="; 220 boolean dumb = false; 221 while ( vi.hasNext() ) 222 { 223 if ( dumb ) 224 { 225 str += ", "; 226 } 227 str += vi.next(); 228 dumb = true; 229 } 230 str += "\n"; 231 } 232 233 return str; 234 } 235 236 237 /** 238 * Description of the Method 239 * 240 * @param s Description of the Parameter 241 * @exception IOException Description of the Exception 242 */ 243 private void fetch_header_and_packet( InputStream s ) 244 throws IOException 245 { 246 247 // read in the minimal packet header 248 byte[] head = new byte[27]; 249 int bytes = s.read( head ); 250 //System.err.println("\nDEBUG: bytes = "+bytes); 251 if ( bytes < 27 ) 252 { 253 throw new IOException( "Not enough bytes in header" ); 254 } 255 256 if ( !"OggS".equals( new String( head, 0, 4 ) ) ) 257 { 258 throw new IOException( "Not a valid Ogg Vorbis file" ); 259 } 260 261 int headerbytes = ( touint( head[26] ) ) + 27; 262 //System.err.println("DEBUG: headerbytes = "+headerbytes); 263 264 // get the rest of the header 265 byte[] head_rest = new byte[touint( head[26] )];// :-) that's a pun 266 bytes += s.read( head_rest ); 267 //System.err.println("DEBUG: bytes = "+bytes); 268 header = new byte[headerbytes]; 269 Arrays.fill( header, (byte)0 ); 270 271 // copy the whole header into header 272 System.arraycopy( head, 0, header, 0, 27 ); 273 System.arraycopy( head_rest, 0, header, 27, headerbytes - 27 ); 274 275 if ( bytes < headerbytes ) 276 { 277 String error = 278 "Error reading vorbis file: " + 279 "Not enough bytes for header + seg table"; 280 throw new IOException( error ); 281 } 282 283 int bodybytes = 0; 284 for ( int i = 0; i < header[26]; i++ ) 285 { 286 bodybytes += touint( header[27 + i] ); 287 } 288 //System.err.println("DEBUG: bodybytes = "+bodybytes); 289 290 packet = new byte[bodybytes]; 291 Arrays.fill( packet, (byte)0 ); 292 bytes += s.read( packet ); 293 //System.err.println("DEBUG: bytes = "+bytes); 294 295 if ( bytes < headerbytes + bodybytes ) 296 { 297 String error = 298 "Error reading vorbis file: " + 299 "Not enough bytes for header + body"; 300 throw new IOException( error ); 301 } 302 303 byte[] oldsum = new byte[4]; 304 System.arraycopy( header, 22, oldsum, 0, 4 );// read existing checksum 305 Arrays.fill( header, 22, 22 + 4, (byte)0 );// clear for calculation of checksum 306 307 byte[] newsum = checksum(); 308 if ( !( new String( oldsum ) ).equals( new String( newsum ) ) ) 309 { 310 System.err.println( "checksum failed" ); 311 System.err.println( "old checksum: " + 312 oldsum[0] + "|" + oldsum[1] + "|" + oldsum[2] + "|" + oldsum[3] ); 313 System.err.println( "new checksum: " + 314 newsum[0] + "|" + newsum[1] + "|" + newsum[2] + "|" + newsum[3] ); 315 } 316 317 } 318 319 320 /** 321 * Description of the Method 322 * 323 * @exception IOException Description of the Exception 324 */ 325 private void interpret_header_packet() 326 throws IOException 327 { 328 byte packet_type = packet[0]; 329 switch ( packet_type ) 330 { 331 case 1: 332 //System.err.println("DEBUG: got header packet"); 333 if ( rate != 0 ) 334 { 335 throw new IOException( "Invalid vorbis file: info already fetched" ); 336 } 337 fetch_info_info(); 338 break; 339 case 3: 340 //System.err.println("DEBUG: got comment packet"); 341 if ( rate == 0 ) 342 { 343 throw new IOException( "Invalid vorbis file: header not complete" ); 344 } 345 fetch_comment_info(); 346 break; 347 case 5: 348 throw new IOException( "Invalid vorbis file: header not complete" ); 349 default: 350 throw new IOException( "Invalid vorbis file: bad packet header" ); 351 } 352 } 353 354 355 /** 356 * pull the fields from the info header 357 * 358 * @exception IOException Description of the Exception 359 */ 360 private void fetch_info_info() 361 throws IOException 362 { 363 364 // keep track of location in packet 365 int dataptr = 1;// should have already read packet[0] for packet type 366 367 String str = new String( packet, dataptr, 6 ); 368 dataptr += 6; 369 if ( !"vorbis".equals( str ) ) 370 { 371 throw new IOException( "Not a vorbis header" ); 372 } 373 dataptr += 4;// skip version (4 bytes) 374 375 channels = packet[dataptr++];// 1 byte 376 377 rate = toulong( read32( packet, dataptr ) ); 378 dataptr += 4;// just read 4 bytes 379 380 bitrate_upper = toulong( read32( packet, dataptr ) ); 381 dataptr += 4;// just read 4 bytes 382 383 bitrate_nominal = toulong( read32( packet, dataptr ) ); 384 dataptr += 4;// just read 4 bytes 385 386 bitrate_lower = toulong( read32( packet, dataptr ) ); 387 dataptr += 4;// just read 4 bytes 388 389 dataptr++;// skip block sizes (4 bits each for a total of 1 byte) 390 391 byte eop = packet[dataptr++]; 392 if ( eop != 1 ) 393 { 394 throw new IOException( "End of packet expected but not found" ); 395 } 396 } 397 398 399 /** 400 * Description of the Method 401 * 402 * @exception IOException Description of the Exception 403 */ 404 private void fetch_comment_info() 405 throws IOException 406 { 407 int dataptr = 1; 408 409 String str = new String( packet, dataptr, 6 ); 410 dataptr += 6; 411 if ( !"vorbis".equals( str ) ) 412 { 413 throw new IOException( "Not a vorbis header" ); 414 } 415 416 comment = new Hashtable(); 417 418 long len = toulong( read32( packet, dataptr ) ); 419 int lenInt = Long.valueOf(len).intValue(); 420 //System.err.println("DEBUG: vendor string length = "+len); 421 dataptr += 4; 422 423 /* 424 * FIXME: Casting len to int here means possible loss of data. 425 * I don't know who would have a comment header big 426 * enough to cause this, but the spec says this is 427 * an unsigned 32 bit int. 428 * Damn java for not having unsigned ints (or it should 429 * at least allow the use of 64 bit longs more frequently) 430 * If I get a chance i'll write a wrapper for this(maybe). 431 */ 432 vendor = new String( packet, dataptr, lenInt ); 433 dataptr += lenInt; 434 435 // FIXME: similar problem to the vendor string length above 436 comments = Long.valueOf(toulong( read32( packet, dataptr ) )).intValue(); 437 //System.out.println( "DEBUG: number of comments: "+comments); 438 dataptr += 4; 439 int validComment = -1; 440 441 for ( int i = 0; i < comments; i++ ) 442 { 443 444 // read comment 445 len = toulong( read32( packet, dataptr ) ); 446 //System.out.println( "DEBUG: length of current comment: "+len); 447 if (len>0) 448 { 449 lenInt = Long.valueOf(len).intValue(); 450 dataptr += 4; 451 // FIXME: same problem as vendor string 452 String cmnt = new String( packet, dataptr, lenInt ); 453 dataptr += lenInt; 454 455 // parse and store 456 457 validComment = cmnt.indexOf( '=' ); 458 if (validComment >0) 459 { 460 String name = cmnt.substring( 0, validComment ); 461 String value = cmnt.substring( validComment + 1 ); 462 if ( comment.containsKey( name ) ) 463 { 464 Vector tmp = (Vector)comment.get( name.toLowerCase() ); 465 tmp.add( value ); 466 } 467 else 468 { 469 Vector tmp = new Vector(); 470 tmp.add( value ); 471 comment.put( name.toLowerCase(), tmp ); 472 } 473 } 474 } 475 } 476 } 477 478 479 /** 480 * Description of the Method 481 * 482 * @param data Description of the Parameter 483 * @param ptr Description of the Parameter 484 * @return Description of the Return Value 485 */ 486 private int read32( byte[] data, int ptr ) 487 { 488 int val = 0; 489 val = ( touint( data[ptr] ) & 0x000000ff ); 490 val |= ( ( touint( data[ptr + 1] ) << 8 ) & 0x0000ff00 ); 491 val |= ( ( touint( data[ptr + 2] ) << 16 ) & 0x00ff0000 ); 492 val |= ( ( touint( data[ptr + 3] ) << 24 ) & 0xff000000 ); 493 return val; 494 } 495 496 497 /** 498 * Description of the Method 499 * 500 * @return Description of the Return Value 501 */ 502 private byte[] checksum() 503 { 504 long crc_reg = 0; 505 506 for ( int i = 0; i < header.length; i++ ) 507 { 508 int tmp = (int)( ( ( crc_reg >>> 24 ) & 0xff ) ^ touint( header[i] ) ); 509 crc_reg = ( crc_reg << 8 ) ^ crc_lookup[tmp]; 510 crc_reg &= 0xffffffff; 511 } 512 for ( int i = 0; i < packet.length; i++ ) 513 { 514 int tmp = (int)( ( ( crc_reg >>> 24 ) & 0xff ) ^ touint( packet[i] ) ); 515 crc_reg = ( crc_reg << 8 ) ^ crc_lookup[tmp]; 516 crc_reg &= 0xffffffff; 517 } 518 519 byte[] sum = new byte[4]; 520 sum[0] = (byte)( crc_reg & 0xffL ); 521 sum[1] = (byte)( ( crc_reg >>> 8 ) & 0xffL ); 522 sum[2] = (byte)( ( crc_reg >>> 16 ) & 0xffL ); 523 sum[3] = (byte)( ( crc_reg >>> 24 ) & 0xffL ); 524 525 return sum; 526 } 527 528 529 /** 530 * Description of the Method 531 * 532 * @param index Description of the Parameter 533 * @return Description of the Return Value 534 */ 535 private long _ogg_crc_entry( long index ) 536 { 537 long r; 538 539 r = index << 24; 540 for ( int i = 0; i < 8; i++ ) 541 { 542 if ( ( r & 0x80000000L ) != 0 ) 543 { 544 r = ( r << 1 ) ^ 0x04c11db7L; 545 } 546 else 547 { 548 r <<= 1; 549 } 550 } 551 return ( r & 0xffffffff ); 552 } 553 554 555 /** 556 * Description of the Method 557 * 558 * @param n Description of the Parameter 559 * @return Description of the Return Value 560 */ 561 private long toulong( int n ) 562 { 563 return ( n & 0xffffffffL ); 564 } 565 566 567 /** 568 * Description of the Method 569 * 570 * @param n Description of the Parameter 571 * @return Description of the Return Value 572 */ 573 private int touint( byte n ) 574 { 575 return ( n & 0xff ); 576 } 577 578} 579