/*
* Java port of ogg demultiplexer.
* Copyright (c) 2004 Jonathan Hueber.
*
* License conditions are the same as OggVorbis. See README.
* 1a39e335700bec46ae31a38e2156a898
*/
/********************************************************************
* *
* THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. *
* USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS *
* GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
* IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
* *
* THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 *
* by the XIPHOPHORUS Company http://www.xiph.org/ *
* *
********************************************************************/
package net.sourceforge.jffmpeg.codecs.audio.vorbis;
import javax.media.Codec;
import javax.media.Format;
import javax.media.format.AudioFormat;
import javax.media.Buffer;
import net.sourceforge.jffmpeg.JMFCodec;
import java.awt.Dimension;
import net.sourceforge.jffmpeg.codecs.audio.vorbis.floor.*;
import net.sourceforge.jffmpeg.codecs.audio.vorbis.residue.*;
import net.sourceforge.jffmpeg.codecs.audio.vorbis.mapping.*;
import net.sourceforge.jffmpeg.codecs.audio.vorbis.mapping.Mdct;
/**
* Vorbis Codec
*/
public class VorbisDecoder implements Codec, JMFCodec {
public static final boolean debug = false;
/* Input data format */
private AudioFormat inputFormat;
/* Header information */
private int headersRequired = 3;
/* Audio information */
private int channels;
private int[] blocksize = new int[ 2 ];
/* Pluggable Modules */
private CodeBook[] codeBooks;
private Floor[] floor_param;
private Residue[] residue_param;
private Mapping[] mapping_param;
private Mode[] modes_param;
/* Modified discrete cosine transforms */
private Mdct[] mdct = new Mdct[ 2 ];
/* Bitreader */
private OggReader oggRead = new OggReader();
/* Retrieve modules */
public CodeBook getCodeBook( int i ) {
return codeBooks[i];
}
public Floor getFloor( int i ) {
return floor_param[i];
}
public Residue getResidue( int i ) {
return residue_param[i];
}
public Mapping getMapping( int i ) {
return mapping_param[i];
}
public Mode getMode( int i ) {
return modes_param[i];
}
public Mdct getMdct() {
return mdct[ W?1:0 ];
}
/**
* Codec management
*/
public Format[] getSupportedInputFormats() {
return new Format[] { new AudioFormat( "vorbis" ) };
}
public Format[] getSupportedOutputFormats(Format format) {
return new Format[] { new AudioFormat( "LINEAR" ) };
}
public Format setInputFormat( Format format ) {
inputFormat = (AudioFormat)format;
return format;
}
public Format setOutputFormat( Format format ) {
return new AudioFormat("LINEAR", inputFormat.getSampleRate(),
inputFormat.getSampleSizeInBits() > 0 ? inputFormat.getSampleSizeInBits() : 16,
inputFormat.getChannels(),
0, 1);
}
private static final int ilog2( int v ) {
int c = 0;
for ( ; v > 0; v >>= 1 ) c++;
return c;
}
/* Read an OGG int */
private int readInt( byte[] buffer, int offset ) {
int ret = 0;
ret = (ret << 8 ) | (buffer[ offset + 3 ] & 0xff);
ret = (ret << 8 ) | (buffer[ offset + 2 ] & 0xff);
ret = (ret << 8 ) | (buffer[ offset + 1 ] & 0xff);
ret = (ret << 8 ) | (buffer[ offset + 0 ] & 0xff);
return ret;
}
/* Read an OGG 24 bit int */
private int read24Bits( byte[] buffer, int offset ) {
int ret = 0;
ret = (ret << 8 ) | (buffer[ offset + 2 ] & 0xff);
ret = (ret << 8 ) | (buffer[ offset + 1 ] & 0xff);
ret = (ret << 8 ) | (buffer[ offset + 0 ] & 0xff);
return ret;
}
/* Read an OGG 16 bit int */
private int read16Bits( byte[] buffer, int offset ) {
int ret = 0;
ret = (ret << 8 ) | (buffer[ offset + 1 ] & 0xff);
ret = (ret << 8 ) | (buffer[ offset + 0 ] & 0xff);
return ret;
}
private void vorbis_unpack_info( byte[] data, int offset, int length ) {
int version = readInt( data, offset );
channels = data[ offset + 4 ] & 0xff;
int rate = readInt( data, offset + 5 );
int bitrate_upper = readInt( data, offset + 9 );
int bitrate_nominal = readInt( data, offset + 13 );
int bitrate_lower = readInt( data, offset + 17 );
blocksize[0] = 1 << (data[ offset + 21 ] & 0xf);
blocksize[1] = 1 << ((data[ offset + 21 ]>>4) & 0xf);
int pad = data[ offset + 22 ] & 1;
if ( version != 0 ) throw new Error( "Unsupported Vorbis version" );
if ( channels < 1 ) throw new Error( "Illegal number of channels" );
if ( rate < 1 ) throw new Error( "Illegal rate" );
if ( blocksize[0] < 8 || blocksize[1] < blocksize[0] ) throw new Error ( "Illegal Block Size" );
if ( pad !=1 ) throw new Error( "Illegal pad" );
mdct[ 0 ] = new Mdct( blocksize[0] );
mdct[ 1 ] = new Mdct( blocksize[1] );
// System.out.println( "Rate: " + rate );
}
private void vorbis_unpack_comment( byte[] data, int offset, int length ) {
int vendorLength = readInt( data, offset );
String vendor = new String( data, offset + 4, vendorLength );
// System.out.println( "Vendor: " + vendor );
offset += 4 + vendorLength;
String[] comments = new String[ readInt( data, offset ) ];
offset += 4;
for ( int i = 0; i < comments.length; i++ ) {
int stringLength = readInt( data, offset );
comments[ i ] = new String( data, offset + 4, stringLength );
offset += 4 + stringLength;
// System.out.println( "Comment: " + comments[ i ] );
}
int pad = data[ offset ] & 1;
if ( pad != 1 ) throw new Error ( "Illegal pad" );
}
private int modebits;
private void vorbis_unpack_books( byte[] data, int offset, int length ) {
oggRead.setData( data, offset );
/* Extract codebooks */
int books = (int)oggRead.getBits(8) + 1;
codeBooks = new CodeBook[ books ];
for ( int i = 0; i < books; i++ ) {
codeBooks[ i ] = new CodeBook();
codeBooks[ i ].unpack( oggRead );
}
/* Parse times */
int times = (int)oggRead.getBits(6) + 1;
for( int i = 0; i < times; i++ ) {
oggRead.getBits(16);
}
/* Floors */
int floors = (int)oggRead.getBits(6) + 1;
floor_param = new Floor[ floors ];
for ( int i = 0; i < floors; i++ ) {
int floor_type = (int)oggRead.getBits(16);
// System.out.println( "floortype " + floor_type );
switch( floor_type ) {
case 0: {
/* floor0_exportbundle */
floor_param[ i ] = new Floor0();
break;
}
case 1: {
/* floor1_exportbundle */
floor_param[ i ] = new Floor1();
break;
}
default: {
throw new Error( "Unrecognised Floor" );
}
}
floor_param[ i ].unpack( oggRead );
}
/* Residues */
int residues = (int)oggRead.getBits(6) + 1;
residue_param = new Residue[ residues ];
for ( int i = 0; i < residues; i++ ) {
int residue_type = (int)oggRead.getBits( 16 );
// System.out.println( "residuetype " + residue_type );
switch ( residue_type ) {
case 0: {
/* residue0_exportbundle */
residue_param[ i ] = new Residue0();
break;
}
case 1: {
/* residue1_exportbundle */
residue_param[ i ] = new Residue1();
break;
}
case 2: {
/* residue2_exportbundle */
residue_param[ i ] = new Residue2();
break;
}
default: {
throw new Error( "Unrecognised Residue" );
}
}
residue_param[ i ].unpack( oggRead );
}
/* Mapping */
int mappings = (int)oggRead.getBits(6) + 1;
mapping_param = new Mapping[ residues ];
for ( int i = 0; i < mappings; i++ ) {
int mapping_type = (int)oggRead.getBits( 16 );
// System.out.println( "mappingtype " + mapping_type );
switch ( mapping_type ) {
case 0: {
/* mapping0_exportbundle */
mapping_param[ i ] = new Mapping0( this );
break;
}
default: {
throw new Error( "Unrecognised Mapping" );
}
}
mapping_param[ i ].unpack( oggRead, channels );
}
/* Modes */
int modes = (int)oggRead.getBits(6) + 1;
modebits = ilog2( modes - 1 );
modes_param = new Mode[ residues ];
for ( int i = 0; i < modes; i++ ) {
// System.out.println( "Mode" );
modes_param[ i ] = new Mode();
modes_param[ i ].unpack( oggRead );
}
/* Padding */
if ( oggRead.getBits( 1 ) != 1 ) throw new Error ( "Padding error" );
/* Decode Books */
for ( int i = 0; i < books; i++ ) {
codeBooks[ i ].initDecode();
}
/* Initialize Floors and Residues */
for ( int i = 0; i < floor_param.length; i++ ) {
floor_param[ i ].look();
}
for ( int i = 0; i < residue_param.length; i++ ) {
residue_param[ i ].look( this );
}
}
/* Retrieve blockflag */
public boolean getW() {
return W;
}
public int getlW() {
return lW;
}
public int getnW() {
return nW;
}
/* Retrieve blockSize */
public int getBlockSize( int i ) {
return blocksize[i];
}
/* Block management */
private boolean W;
private int lW;
private int nW;
private void vorbis_synthesis( byte[] data, int offset, int length, Buffer output ) {
oggRead.setData( data, offset );
/* Check the packet type */
if( oggRead.getBits(1) != 0 ) {
return;
//throw new Error( "Not Audio" );
}
/* read our mode and pre/post windowsize */
int mode = (int)oggRead.getBits( modebits );
// System.out.println( "Mode " + modebits + " " + mode );
W = modes_param[ mode ].getBlockFlag();
lW = 0;
nW = 0;
if ( W ) {
lW = (int)oggRead.getBits(1);
nW = (int)oggRead.getBits(1);
// System.out.println( "lnW " + nW + " " + lW );
}
// TODO handle Packet headers, etc.
/* unpack_header enforces range checking */
int type = modes_param[ mode ].getMapping();
mapping_param[ type ].inverse( oggRead, this );
mapping_param[ type ].vorbis_synthesis_blockin( this );
mapping_param[ type ].soundOutput( output );
}
private static final int HEADER_INFO = 1;
private static final int HEADER_COMMENT = 3;
private static final int HEADER_BOOKS = 5;
private void decodeSegment( byte[] data, int offset, int length, Buffer output ) {
// System.out.println( "Decoding segment" + headersRequired);
if ( headersRequired != 0 ) {
int packType = data[ offset ];
if ( data[ offset + 1 ] != 'v'
|| data[ offset + 2 ] != 'o'
|| data[ offset + 3 ] != 'r'
|| data[ offset + 4 ] != 'b'
|| data[ offset + 5 ] != 'i'
|| data[ offset + 6 ] != 's' ) {
// throw new Error( "Not a VORBIS header" );
return;
}
offset += 7;
length -= 7;
switch (packType) {
case HEADER_INFO: {
vorbis_unpack_info( data, offset, length );
break;
}
case HEADER_COMMENT: {
vorbis_unpack_comment( data, offset, length );
break;
}
case HEADER_BOOKS: {
vorbis_unpack_books( data, offset, length);
break;
}
default: {
throw new Error( "Invalid header ID: " + packType );
}
}
headersRequired--;
return;
}
vorbis_synthesis( data, offset, length, output );
}
private byte[] packetBuffer = new byte[ 0 ];
private int packetBufferLength = 0;
public int process( Buffer input, Buffer output ) {
output.setFlags( input.getFlags() );
output.setTimeStamp( input.getTimeStamp() );
output.setDuration( input.getDuration() );
output.setLength( 0 );
try {
byte[] data = (byte[])input.getData(); //in.getLength
int dataLength = input.getLength();
/* Parse segments */
int numberOfSegments = data[ 26 ] & 0xff;
int segmentNumber = 0;
int dataPointer = 27 + numberOfSegments;
while ( segmentNumber < numberOfSegments ) {
int length = 0;
do {
length += data[ 27 + segmentNumber ] & 0xff;
segmentNumber++;
} while( data[ 27 + (segmentNumber - 1) ] == -1
&& segmentNumber < numberOfSegments );
/* Copy data to spare buffer */
if ( packetBuffer.length < length + packetBufferLength ) {
byte[] t = packetBuffer;
packetBuffer = new byte[ length + packetBufferLength ];
System.arraycopy(t, 0, packetBuffer,0, packetBufferLength);
}
System.arraycopy( data, dataPointer,
packetBuffer, packetBufferLength, length );
packetBufferLength += length;
/* Is this packet going to wrap to next packet ? */
if ( data[ 27 + (segmentNumber - 1) ] == -1 ) {
continue;
}
// System.out.println( "Segment length " + packetBufferLength );
decodeSegment( packetBuffer, 0, packetBufferLength, output );
dataPointer += length;
packetBufferLength = 0;
}
// System.out.println( "End of packet" );
} catch ( Exception e ) {
System.out.println( e );
e.printStackTrace();
return BUFFER_PROCESSED_FAILED;
} catch( Error e ) {
System.out.println( e );
e.printStackTrace();
}
return BUFFER_PROCESSED_OK;
}
public void open() {
}
public void close() {
}
public void reset() {
}
public String getName() {
return "Vorbis";
}
public Object[] getControls() {
return new Object[ 0 ];
}
public Object getControl( String type ) {
return null;
}
/**
* Implement the Jffmpeg codec interface
*/
public boolean isCodecAvailable() {
return true;
}
/**
* Outofbands video size
*/
public void setVideoSize( Dimension size ) {
}
public void setEncoding( String encoding ) {
}
public void setIsRtp( boolean isRtp ) {
}
public void setIsTruncated( boolean isTruncated ) {
}
}