package org.jboss.media.format.audio.oggvorbis;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;
class VorbisInfo
{
private int channels;
private long rate = 0;
private long bitrate_upper;
private long bitrate_nominal;
private long bitrate_lower;
String vendor;
int comments = 0;
Hashtable comment;
private long[] crc_lookup = new long[256];
private int crc_ready = 0;
private byte[] header;
private byte[] packet;
public VorbisInfo(InputStream s) throws IOException
{
for (int i = 0; i < 256; i++)
crc_lookup[i] = _ogg_crc_entry(i);
fetch_header_and_packet(s);
interpret_header_packet();
fetch_header_and_packet(s);
interpret_header_packet();
}
public int getChannels()
{
return channels;
}
public long getRate()
{
return rate;
}
public long getBitrate()
{
return bitrate_nominal;
}
public Vector getComments(String field)
{
return (Vector) comment.get(field.toLowerCase());
}
public Set getFields()
{
return comment.keySet();
}
private void fetch_header_and_packet(InputStream s) throws IOException
{
byte[] head = new byte[27];
int bytes = s.read(head);
if (bytes < 27)
throw new IOException("Not enough bytes in header");
if (!"OggS".equals(new String(head, 0, 4)))
throw new IOException("Not a valid Ogg Vorbis file");
int headerbytes = (touint(head[26])) + 27;
byte[] head_rest = new byte[touint(head[26])]; bytes += s.read(head_rest);
header = new byte[headerbytes];
Arrays.fill(header, (byte) 0);
System.arraycopy(head, 0, header, 0, 27);
System.arraycopy(head_rest, 0, header, 27, headerbytes - 27);
if (bytes < headerbytes)
{
String error =
"Error reading vorbis file: "
+ "Not enough bytes for header + seg table";
throw new IOException(error);
}
int bodybytes = 0;
for (int i = 0; i < header[26]; i++)
bodybytes += touint(header[27 + i]);
packet = new byte[bodybytes];
Arrays.fill(packet, (byte) 0);
bytes += s.read(packet);
if (bytes < headerbytes + bodybytes)
{
String error =
"Error reading vorbis file: "
+ "Not enough bytes for header + body";
throw new IOException(error);
}
byte[] oldsum = new byte[4];
System.arraycopy(header, 22, oldsum, 0, 4); Arrays.fill(header, 22, 22 + 4, (byte) 0);
byte[] newsum = checksum();
if (!(new String(oldsum)).equals(new String(newsum)))
{
System.err.println("checksum failed");
System.err.println(
"old checksum: "
+ oldsum[0]
+ "|"
+ oldsum[1]
+ "|"
+ oldsum[2]
+ "|"
+ oldsum[3]);
System.err.println(
"new checksum: "
+ newsum[0]
+ "|"
+ newsum[1]
+ "|"
+ newsum[2]
+ "|"
+ newsum[3]);
}
}
private void interpret_header_packet() throws IOException
{
byte packet_type = packet[0];
switch (packet_type)
{
case 1 :
if (rate != 0)
throw new IOException("Invalid vorbis file: info already fetched");
fetch_info_info();
break;
case 3 :
if (rate == 0)
throw new IOException("Invalid vorbis file: header not complete");
fetch_comment_info();
break;
case 5 :
throw new IOException("Invalid vorbis file: header not complete");
default :
throw new IOException("Invalid vorbis file: bad packet header");
}
}
private void fetch_info_info() throws IOException
{
int dataptr = 1;
String str = new String(packet, dataptr, 6);
dataptr += 6;
if (!"vorbis".equals(str))
throw new IOException("Not a vorbis header");
dataptr += 4;
channels = packet[dataptr++];
rate = toulong(read32(packet, dataptr));
dataptr += 4;
bitrate_upper = toulong(read32(packet, dataptr));
dataptr += 4;
bitrate_nominal = toulong(read32(packet, dataptr));
dataptr += 4;
bitrate_lower = toulong(read32(packet, dataptr));
dataptr += 4;
dataptr++;
byte eop = packet[dataptr++];
if (eop != 1)
throw new IOException("End of packet expected but not found");
}
private void fetch_comment_info() throws IOException
{
int dataptr = 1;
String str = new String(packet, dataptr, 6);
dataptr += 6;
if (!"vorbis".equals(str))
throw new IOException("Not a vorbis header");
comment = new Hashtable();
long len = toulong(read32(packet, dataptr));
dataptr += 4;
vendor = new String(packet, dataptr, (int) len);
dataptr += len;
comments = (int) toulong(read32(packet, dataptr));
dataptr += 4;
for (int i = 0; i < comments; i++)
{
len = toulong(read32(packet, dataptr));
dataptr += 4;
String cmnt = new String(packet, dataptr, (int) len);
dataptr += len;
String name = cmnt.substring(0, cmnt.indexOf('='));
String value = cmnt.substring(cmnt.indexOf('=') + 1);
if (comment.containsKey(name))
{
Vector tmp = (Vector) comment.get(name.toLowerCase());
tmp.add(value);
}
else
{
Vector tmp = new Vector();
tmp.add(value);
comment.put(name.toLowerCase(), tmp);
}
}
}
private int read32(byte[] data, int ptr)
{
int val = 0;
val = (touint(data[ptr]) & 0x000000ff);
val |= ((touint(data[ptr + 1]) << 8) & 0x0000ff00);
val |= ((touint(data[ptr + 2]) << 16) & 0x00ff0000);
val |= ((touint(data[ptr + 3]) << 24) & 0xff000000);
return val;
}
private byte[] checksum()
{
long crc_reg = 0;
for (int i = 0; i < header.length; i++)
{
int tmp = (int) (((crc_reg >>> 24) & 0xff) ^ touint(header[i]));
crc_reg = (crc_reg << 8) ^ crc_lookup[tmp];
crc_reg &= 0xffffffff;
}
for (int i = 0; i < packet.length; i++)
{
int tmp = (int) (((crc_reg >>> 24) & 0xff) ^ touint(packet[i]));
crc_reg = (crc_reg << 8) ^ crc_lookup[tmp];
crc_reg &= 0xffffffff;
}
byte[] sum = new byte[4];
sum[0] = (byte) (crc_reg & 0xffL);
sum[1] = (byte) ((crc_reg >>> 8) & 0xffL);
sum[2] = (byte) ((crc_reg >>> 16) & 0xffL);
sum[3] = (byte) ((crc_reg >>> 24) & 0xffL);
return sum;
}
private long _ogg_crc_entry(long index)
{
long r;
r = index << 24;
for (int i = 0; i < 8; i++)
{
if ((r & 0x80000000L) != 0)
{
r = (r << 1) ^ 0x04c11db7L;
}
else
{
r <<= 1;
}
}
return (r & 0xffffffff);
}
private long toulong(int n)
{
return (n & 0xffffffffL);
}
private int touint(byte n)
{
return (n & 0xff);
}
public String toString()
{
String str = "";
str += channels + " channels at " + rate + "Hz\n";
str += bitrate_nominal / 1000 + "kbps (average bitrate)\n";
Iterator fields = comment.keySet().iterator();
while (fields.hasNext())
{
String name = (String) fields.next();
Vector values = (Vector) comment.get(name);
Iterator vi = values.iterator();
str += name + "=";
boolean dumb = false;
while (vi.hasNext())
{
if (dumb)
str += ", ";
str += vi.next();
dumb = true;
}
str += "\n";
}
return str;
}
static void main(String[] args) throws Exception
{
if (args.length != 1)
{
System.err.println("usage:\tjava VorbisInfo <ogg vorbis file>");
System.exit(1);
}
BufferedInputStream b =
new BufferedInputStream(new FileInputStream(args[0]));
VorbisInfo vi = new VorbisInfo(b);
System.out.println("\n" + vi);
b.close();
}
public Hashtable getComments() {
return comment;
}
}