/*
* VorbisAudioFileReader.
*
* JavaZOOM : vorbisspi@javazoom.net
* http://www.javazoom.net
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
package javazoom.spi.vorbis.sampled.file;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.StringTokenizer;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import org.tritonus.share.TDebug;
import org.tritonus.share.sampled.file.TAudioFileReader;
import com.jcraft.jogg.Packet;
import com.jcraft.jogg.Page;
import com.jcraft.jogg.StreamState;
import com.jcraft.jogg.SyncState;
import com.jcraft.jorbis.Block;
import com.jcraft.jorbis.Comment;
import com.jcraft.jorbis.DspState;
import com.jcraft.jorbis.Info;
import com.jcraft.jorbis.JOrbisException;
import com.jcraft.jorbis.VorbisFile;
/**
* This class implements the AudioFileReader class and provides an
* Ogg Vorbis file reader for use with the Java Sound Service Provider Interface.
*/
public class VorbisAudioFileReader extends TAudioFileReader
{
private SyncState oggSyncState_ = null;
private StreamState oggStreamState_ = null;
private Page oggPage_ = null;
private Packet oggPacket_ = null;
private Info vorbisInfo = null;
private Comment vorbisComment = null;
private DspState vorbisDspState = null;
private Block vorbisBlock = null;
private int bufferMultiple_ = 4;
private int bufferSize_ = bufferMultiple_ * 256 * 2;
private byte[] buffer = null;
private int bytes = 0;
private int index = 0;
private InputStream oggBitStream_ = null;
private static final int INITAL_READ_LENGTH = 64000;
private static final int MARK_LIMIT = INITAL_READ_LENGTH + 1;
public VorbisAudioFileReader()
{
super(MARK_LIMIT, true);
}
/**
* Return the AudioFileFormat from the given file.
*/
public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException
{
if (TDebug.TraceAudioFileReader) TDebug.out("getAudioFileFormat(File file)");
InputStream inputStream = null;
try
{
inputStream = new BufferedInputStream(new FileInputStream(file));
inputStream.mark(MARK_LIMIT);
AudioFileFormat aff = getAudioFileFormat(inputStream);
inputStream.reset();
// Get Vorbis file info such as length in seconds.
VorbisFile vf = new VorbisFile(file.getAbsolutePath());
return getAudioFileFormat(inputStream,(int) file.length(), (int) Math.round((vf.time_total(-1))*1000));
}
catch (JOrbisException e)
{
throw new IOException(e.getMessage());
}
finally
{
if (inputStream != null) inputStream.close();
}
}
/**
* Return the AudioFileFormat from the given URL.
*/
public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException
{
if (TDebug.TraceAudioFileReader) TDebug.out("getAudioFileFormat(URL url)");
InputStream inputStream = url.openStream();
try
{
return getAudioFileFormat(inputStream);
}
finally
{
if (inputStream != null) inputStream.close();
}
}
/**
* Return the AudioFileFormat from the given InputStream.
*/
public AudioFileFormat getAudioFileFormat(InputStream inputStream) throws UnsupportedAudioFileException, IOException
{
if (TDebug.TraceAudioFileReader) TDebug.out("getAudioFileFormat(InputStream inputStream)");
try
{
if (!inputStream.markSupported()) inputStream = new BufferedInputStream(inputStream);
inputStream.mark(MARK_LIMIT);
return getAudioFileFormat(inputStream, AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED);
}
finally
{
inputStream.reset();
}
}
/**
* Return the AudioFileFormat from the given InputStream and length in bytes.
*/
public AudioFileFormat getAudioFileFormat(InputStream inputStream, long medialength) throws UnsupportedAudioFileException, IOException
{
return getAudioFileFormat(inputStream, (int) medialength, AudioSystem.NOT_SPECIFIED);
}
/**
* Return the AudioFileFormat from the given InputStream, length in bytes and length in milliseconds.
*/
protected AudioFileFormat getAudioFileFormat(InputStream bitStream, int mediaLength, int totalms) throws UnsupportedAudioFileException, IOException
{
HashMap aff_properties = new HashMap();
HashMap af_properties = new HashMap();
if (totalms == AudioSystem.NOT_SPECIFIED)
{
totalms = 0;
}
if (totalms <= 0)
{
totalms = 0;
}
else
{
aff_properties.put("duration",new Long(totalms*1000));
}
oggBitStream_ = bitStream;
init_jorbis();
index = 0;
try
{
readHeaders(aff_properties, af_properties);
}
catch (IOException ioe)
{
if (TDebug.TraceAudioFileReader)
{
TDebug.out(ioe.getMessage());
}
throw new UnsupportedAudioFileException(ioe.getMessage());
}
String dmp = vorbisInfo.toString();
if (TDebug.TraceAudioFileReader)
{
TDebug.out(dmp);
}
int ind = dmp.lastIndexOf("bitrate:");
int minbitrate = -1;
int nominalbitrate = -1;
int maxbitrate = -1;
if (ind != -1)
{
dmp = dmp.substring(ind + 8, dmp.length());
StringTokenizer st = new StringTokenizer(dmp, ",");
if (st.hasMoreTokens())
{
minbitrate = Integer.parseInt(st.nextToken());
}
if (st.hasMoreTokens())
{
nominalbitrate = Integer.parseInt(st.nextToken());
}
if (st.hasMoreTokens())
{
maxbitrate = Integer.parseInt(st.nextToken());
}
}
if (nominalbitrate > 0) af_properties.put("bitrate",new Integer(nominalbitrate));
af_properties.put("vbr",new Boolean(true));
if (minbitrate > 0) aff_properties.put("ogg.bitrate.min.bps",new Integer(minbitrate));
if (maxbitrate > 0) aff_properties.put("ogg.bitrate.max.bps",new Integer(maxbitrate));
if (nominalbitrate > 0) aff_properties.put("ogg.bitrate.nominal.bps",new Integer(nominalbitrate));
if (vorbisInfo.channels > 0) aff_properties.put("ogg.channels",new Integer(vorbisInfo.channels));
if (vorbisInfo.rate > 0) aff_properties.put("ogg.frequency.hz",new Integer(vorbisInfo.rate));
if (mediaLength > 0) aff_properties.put("ogg.length.bytes",new Integer(mediaLength));
aff_properties.put("ogg.version",new Integer(vorbisInfo.version));
//AudioFormat.Encoding encoding = VorbisEncoding.VORBISENC;
//AudioFormat format = new VorbisAudioFormat(encoding, vorbisInfo.rate, AudioSystem.NOT_SPECIFIED, vorbisInfo.channels, AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED, true,af_properties);
// Patch from MS to ensure more SPI compatibility ...
float frameRate = -1;
if (nominalbitrate > 0) frameRate = nominalbitrate / 8;
else if (minbitrate > 0) frameRate = minbitrate / 8;
AudioFormat.Encoding encoding = VorbisEncoding.VORBISENC;
// New Patch from MS:
AudioFormat format = new VorbisAudioFormat(encoding, vorbisInfo.rate, AudioSystem.NOT_SPECIFIED, vorbisInfo.channels, 1, frameRate, false, af_properties);
// Patch end
return new VorbisAudioFileFormat(VorbisFileFormatType.OGG, format, AudioSystem.NOT_SPECIFIED, mediaLength,aff_properties);
}
/**
* Return the AudioInputStream from the given InputStream.
*/
public AudioInputStream getAudioInputStream(InputStream inputStream) throws UnsupportedAudioFileException, IOException
{
if (TDebug.TraceAudioFileReader) TDebug.out("getAudioInputStream(InputStream inputStream)");
return getAudioInputStream(inputStream, AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED);
}
/**
* Return the AudioInputStream from the given InputStream.
*/
public AudioInputStream getAudioInputStream(InputStream inputStream, int medialength, int totalms) throws UnsupportedAudioFileException, IOException
{
if (TDebug.TraceAudioFileReader) TDebug.out("getAudioInputStream(InputStream inputStreamint medialength, int totalms)");
try
{
if (!inputStream.markSupported()) inputStream = new BufferedInputStream(inputStream);
inputStream.mark(MARK_LIMIT);
AudioFileFormat audioFileFormat = getAudioFileFormat(inputStream, medialength, totalms);
inputStream.reset();
return new AudioInputStream(inputStream, audioFileFormat.getFormat(), audioFileFormat.getFrameLength());
}
catch (UnsupportedAudioFileException e)
{
inputStream.reset();
throw e;
}
catch (IOException e)
{
inputStream.reset();
throw e;
}
}
/**
* Return the AudioInputStream from the given File.
*/
public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException
{
if (TDebug.TraceAudioFileReader) TDebug.out("getAudioInputStream(File file)");
InputStream inputStream = new FileInputStream(file);
try
{
return getAudioInputStream(inputStream);
}
catch (UnsupportedAudioFileException e)
{
if (inputStream != null) inputStream.close();
throw e;
}
catch (IOException e)
{
if (inputStream != null) inputStream.close();
throw e;
}
}
/**
* Return the AudioInputStream from the given URL.
*/
public AudioInputStream getAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException
{
if (TDebug.TraceAudioFileReader) TDebug.out("getAudioInputStream(URL url)");
InputStream inputStream = url.openStream();
try
{
return getAudioInputStream(inputStream);
}
catch (UnsupportedAudioFileException e)
{
if (inputStream != null) inputStream.close();
throw e;
}
catch (IOException e)
{
if (inputStream != null) inputStream.close();
throw e;
}
}
/**
* Reads headers and comments.
*/
private void readHeaders(HashMap aff_properties, HashMap af_properties) throws IOException
{
if(TDebug.TraceAudioConverter) TDebug.out("readHeaders(");
index = oggSyncState_.buffer(bufferSize_);
buffer = oggSyncState_.data;
bytes = readFromStream(buffer, index, bufferSize_);
if(bytes == -1)
{
if(TDebug.TraceAudioConverter) TDebug.out("Cannot get any data from selected Ogg bitstream.");
throw new IOException("Cannot get any data from selected Ogg bitstream.");
}
oggSyncState_.wrote(bytes);
if(oggSyncState_.pageout(oggPage_) != 1)
{
if(bytes < bufferSize_)
{
throw new IOException("EOF");
}
if(TDebug.TraceAudioConverter) TDebug.out("Input does not appear to be an Ogg bitstream.");
throw new IOException("Input does not appear to be an Ogg bitstream.");
}
oggStreamState_.init(oggPage_.serialno());
vorbisInfo.init();
vorbisComment.init();
aff_properties.put("ogg.serial",new Integer(oggPage_.serialno()));
if(oggStreamState_.pagein(oggPage_) < 0)
{
// error; stream version mismatch perhaps
if(TDebug.TraceAudioConverter) TDebug.out("Error reading first page of Ogg bitstream data.");
throw new IOException("Error reading first page of Ogg bitstream data.");
}
if(oggStreamState_.packetout(oggPacket_) != 1)
{
// no page? must not be vorbis
if(TDebug.TraceAudioConverter) TDebug.out("Error reading initial header packet.");
throw new IOException("Error reading initial header packet.");
}
if(vorbisInfo.synthesis_headerin(vorbisComment, oggPacket_) < 0)
{
// error case; not a vorbis header
if(TDebug.TraceAudioConverter) TDebug.out("This Ogg bitstream does not contain Vorbis audio data.");
throw new IOException("This Ogg bitstream does not contain Vorbis audio data.");
}
int i = 0;
while(i < 2)
{
while(i < 2)
{
int result = oggSyncState_.pageout(oggPage_);
if(result == 0)
{
break;
} // Need more data
if(result == 1)
{
oggStreamState_.pagein(oggPage_);
while(i < 2)
{
result = oggStreamState_.packetout(oggPacket_);
if(result == 0)
{
break;
}
if(result == -1)
{
if(TDebug.TraceAudioConverter) TDebug.out("Corrupt secondary header. Exiting.");
throw new IOException("Corrupt secondary header. Exiting.");
}
vorbisInfo.synthesis_headerin(vorbisComment, oggPacket_);
i++;
}
}
}
index = oggSyncState_.buffer(bufferSize_);
buffer = oggSyncState_.data;
bytes = readFromStream(buffer, index, bufferSize_);
if(bytes == -1)
{
break;
}
if(bytes == 0 && i < 2)
{
if(TDebug.TraceAudioConverter) TDebug.out("End of file before finding all Vorbis headers!");
throw new IOException("End of file before finding all Vorbis headers!");
}
oggSyncState_.wrote(bytes);
}
// Read Ogg Vorbis comments.
byte[][] ptr = vorbisComment.user_comments;
String currComment = "";
int c = 0;
for(int j = 0; j < ptr.length; j++)
{
if(ptr[j] == null)
{
break;
}
currComment = (new String(ptr[j], 0, ptr[j].length - 1,"UTF-8")).trim();
if(TDebug.TraceAudioConverter) TDebug.out(currComment);
if (currComment.toLowerCase().startsWith("artist"))
{
aff_properties.put("author",currComment.substring(7));
}
else if (currComment.toLowerCase().startsWith("title"))
{
aff_properties.put("title",currComment.substring(6));
}
else if (currComment.toLowerCase().startsWith("album"))
{
aff_properties.put("album",currComment.substring(6));
}
else if (currComment.toLowerCase().startsWith("date"))
{
aff_properties.put("date",currComment.substring(5));
}
else if (currComment.toLowerCase().startsWith("copyright"))
{
aff_properties.put("copyright",currComment.substring(10));
}
else if (currComment.toLowerCase().startsWith("comment"))
{
aff_properties.put("comment",currComment.substring(8));
}
else if (currComment.toLowerCase().startsWith("genre"))
{
aff_properties.put("ogg.comment.genre",currComment.substring(6));
}
else if (currComment.toLowerCase().startsWith("tracknumber"))
{
aff_properties.put("ogg.comment.track",currComment.substring(12));
}
else
{
c++;
aff_properties.put("ogg.comment.ext."+c,currComment);
}
aff_properties.put("ogg.comment.encodedby",new String(vorbisComment.vendor, 0, vorbisComment.vendor.length - 1));
}
}
/**
* Reads from the oggBitStream_ a specified number of Bytes(bufferSize_) worth
* starting at index and puts them in the specified buffer[].
*
* @return the number of bytes read or -1 if error.
*/
private int readFromStream(byte[] buffer, int index, int bufferSize_)
{
int bytes = 0;
try
{
bytes = oggBitStream_.read(buffer, index, bufferSize_);
}
catch (Exception e)
{
if (TDebug.TraceAudioFileReader)
{
TDebug.out("Cannot Read Selected Song");
}
bytes = -1;
}
return bytes;
}
/**
* Initializes all the jOrbis and jOgg vars that are used for song playback.
*/
private void init_jorbis()
{
oggSyncState_ = new SyncState();
oggStreamState_ = new StreamState();
oggPage_ = new Page();
oggPacket_ = new Packet();
vorbisInfo = new Info();
vorbisComment = new Comment();
vorbisDspState = new DspState();
vorbisBlock = new Block(vorbisDspState);
buffer = null;
bytes = 0;
oggSyncState_.init();
}
}