411 lines
11 KiB
Java
411 lines
11 KiB
Java
|
/*
|
||
|
* 11/19/04 1.0 moved to LGPL.
|
||
|
* 12/12/99 Original verion. mdm@techie.com.
|
||
|
*-----------------------------------------------------------------------
|
||
|
* 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.jl.converter;
|
||
|
|
||
|
import java.io.BufferedInputStream;
|
||
|
import java.io.File;
|
||
|
import java.io.FileInputStream;
|
||
|
import java.io.IOException;
|
||
|
import java.io.InputStream;
|
||
|
import java.io.PrintWriter;
|
||
|
|
||
|
import javazoom.jl.decoder.Bitstream;
|
||
|
import javazoom.jl.decoder.Decoder;
|
||
|
import javazoom.jl.decoder.Header;
|
||
|
import javazoom.jl.decoder.JavaLayerException;
|
||
|
import javazoom.jl.decoder.Obuffer;
|
||
|
|
||
|
/**
|
||
|
* The <code>Converter</code> class implements the conversion of
|
||
|
* an MPEG audio file to a .WAV file. To convert an MPEG audio stream,
|
||
|
* just create an instance of this class and call the convert()
|
||
|
* method, passing in the names of the input and output files. You can
|
||
|
* pass in optional <code>ProgressListener</code> and
|
||
|
* <code>Decoder.Params</code> objects also to customize the conversion.
|
||
|
*
|
||
|
* @author MDM 12/12/99
|
||
|
* @since 0.0.7
|
||
|
*/
|
||
|
public class Converter
|
||
|
{
|
||
|
/**
|
||
|
* Creates a new converter instance.
|
||
|
*/
|
||
|
public Converter()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public synchronized void convert(String sourceName, String destName)
|
||
|
throws JavaLayerException
|
||
|
{
|
||
|
convert(sourceName, destName, null, null);
|
||
|
}
|
||
|
|
||
|
public synchronized void convert(String sourceName, String destName,
|
||
|
ProgressListener progressListener)
|
||
|
throws JavaLayerException
|
||
|
{
|
||
|
convert(sourceName, destName, progressListener, null);
|
||
|
}
|
||
|
|
||
|
|
||
|
public void convert(String sourceName, String destName,
|
||
|
ProgressListener progressListener, Decoder.Params decoderParams)
|
||
|
throws JavaLayerException
|
||
|
{
|
||
|
if (destName.length()==0)
|
||
|
destName = null;
|
||
|
try {
|
||
|
InputStream in = openInput(sourceName);
|
||
|
convert(in, destName, progressListener, decoderParams);
|
||
|
in.close();
|
||
|
} catch(IOException ioe) {
|
||
|
throw new JavaLayerException(ioe.getLocalizedMessage(), ioe);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public synchronized void convert(InputStream sourceStream, String destName,
|
||
|
ProgressListener progressListener, Decoder.Params decoderParams)
|
||
|
throws JavaLayerException
|
||
|
{
|
||
|
if (progressListener==null)
|
||
|
progressListener = PrintWriterProgressListener.newStdOut(
|
||
|
PrintWriterProgressListener.NO_DETAIL);
|
||
|
try {
|
||
|
if (!(sourceStream instanceof BufferedInputStream))
|
||
|
sourceStream = new BufferedInputStream(sourceStream);
|
||
|
int frameCount = -1;
|
||
|
if (sourceStream.markSupported()) {
|
||
|
sourceStream.mark(-1);
|
||
|
frameCount = countFrames(sourceStream);
|
||
|
sourceStream.reset();
|
||
|
}
|
||
|
progressListener.converterUpdate(ProgressListener.UPDATE_FRAME_COUNT, frameCount, 0);
|
||
|
|
||
|
|
||
|
Obuffer output = null;
|
||
|
Decoder decoder = new Decoder(decoderParams);
|
||
|
Bitstream stream = new Bitstream(sourceStream);
|
||
|
|
||
|
if (frameCount==-1)
|
||
|
frameCount = Integer.MAX_VALUE;
|
||
|
|
||
|
int frame = 0;
|
||
|
long startTime = System.currentTimeMillis();
|
||
|
|
||
|
try
|
||
|
{
|
||
|
for (; frame<frameCount; frame++)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
Header header = stream.readFrame();
|
||
|
if (header==null)
|
||
|
break;
|
||
|
|
||
|
progressListener.readFrame(frame, header);
|
||
|
|
||
|
if (output==null)
|
||
|
{
|
||
|
// REVIEW: Incorrect functionality.
|
||
|
// the decoder should provide decoded
|
||
|
// frequency and channels output as it may differ from
|
||
|
// the source (e.g. when downmixing stereo to mono.)
|
||
|
int channels = (header.mode()==Header.SINGLE_CHANNEL) ? 1 : 2;
|
||
|
int freq = header.frequency();
|
||
|
output = new WaveFileObuffer(channels, freq, destName);
|
||
|
decoder.setOutputBuffer(output);
|
||
|
}
|
||
|
|
||
|
Obuffer decoderOutput = decoder.decodeFrame(header, stream);
|
||
|
|
||
|
// REVIEW: the way the output buffer is set
|
||
|
// on the decoder is a bit dodgy. Even though
|
||
|
// this exception should never happen, we test to be sure.
|
||
|
if (decoderOutput!=output)
|
||
|
throw new InternalError("Output buffers are different.");
|
||
|
|
||
|
|
||
|
progressListener.decodedFrame(frame, header, output);
|
||
|
|
||
|
stream.closeFrame();
|
||
|
|
||
|
}
|
||
|
catch (Exception ex)
|
||
|
{
|
||
|
boolean stop = !progressListener.converterException(ex);
|
||
|
|
||
|
if (stop)
|
||
|
{
|
||
|
throw new JavaLayerException(ex.getLocalizedMessage(), ex);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
|
||
|
if (output!=null)
|
||
|
output.close();
|
||
|
}
|
||
|
|
||
|
int time = (int)(System.currentTimeMillis()-startTime);
|
||
|
progressListener.converterUpdate(ProgressListener.UPDATE_CONVERT_COMPLETE,
|
||
|
time, frame);
|
||
|
}
|
||
|
catch (IOException ex)
|
||
|
{
|
||
|
throw new JavaLayerException(ex.getLocalizedMessage(), ex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
protected int countFrames(InputStream in)
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
|
||
|
protected InputStream openInput(String fileName)
|
||
|
throws IOException
|
||
|
{
|
||
|
// ensure name is abstract path name
|
||
|
File file = new File(fileName);
|
||
|
InputStream fileIn = new FileInputStream(file);
|
||
|
BufferedInputStream bufIn = new BufferedInputStream(fileIn);
|
||
|
|
||
|
return bufIn;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* This interface is used by the Converter to provide
|
||
|
* notification of tasks being carried out by the converter,
|
||
|
* and to provide new information as it becomes available.
|
||
|
*/
|
||
|
|
||
|
static public interface ProgressListener
|
||
|
{
|
||
|
public static final int UPDATE_FRAME_COUNT = 1;
|
||
|
|
||
|
/**
|
||
|
* Conversion is complete. Param1 contains the time
|
||
|
* to convert in milliseconds. Param2 contains the number
|
||
|
* of MPEG audio frames converted.
|
||
|
*/
|
||
|
public static final int UPDATE_CONVERT_COMPLETE = 2;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Notifies the listener that new information is available.
|
||
|
*
|
||
|
* @param updateID Code indicating the information that has been
|
||
|
* updated.
|
||
|
*
|
||
|
* @param param1 Parameter whose value depends upon the update code.
|
||
|
* @param param2 Parameter whose value depends upon the update code.
|
||
|
*
|
||
|
* The <code>updateID</code> parameter can take these values:
|
||
|
*
|
||
|
* UPDATE_FRAME_COUNT: param1 is the frame count, or -1 if not known.
|
||
|
* UPDATE_CONVERT_COMPLETE: param1 is the conversion time, param2
|
||
|
* is the number of frames converted.
|
||
|
*/
|
||
|
public void converterUpdate(int updateID, int param1, int param2);
|
||
|
|
||
|
/**
|
||
|
* If the converter wishes to make a first pass over the
|
||
|
* audio frames, this is called as each frame is parsed.
|
||
|
*/
|
||
|
public void parsedFrame(int frameNo, Header header);
|
||
|
|
||
|
/**
|
||
|
* This method is called after each frame has been read,
|
||
|
* but before it has been decoded.
|
||
|
*
|
||
|
* @param frameNo The 0-based sequence number of the frame.
|
||
|
* @param header The Header rerpesenting the frame just read.
|
||
|
*/
|
||
|
public void readFrame(int frameNo, Header header);
|
||
|
|
||
|
/**
|
||
|
* This method is called after a frame has been decoded.
|
||
|
*
|
||
|
* @param frameNo The 0-based sequence number of the frame.
|
||
|
* @param header The Header rerpesenting the frame just read.
|
||
|
* @param o The Obuffer the deocded data was written to.
|
||
|
*/
|
||
|
public void decodedFrame(int frameNo, Header header, Obuffer o);
|
||
|
|
||
|
/**
|
||
|
* Called when an exception is thrown during while converting
|
||
|
* a frame.
|
||
|
*
|
||
|
* @param t The <code>Throwable</code> instance that
|
||
|
* was thrown.
|
||
|
*
|
||
|
* @return <code>true</code> to continue processing, or false
|
||
|
* to abort conversion.
|
||
|
*
|
||
|
* If this method returns <code>false</code>, the exception
|
||
|
* is propagated to the caller of the convert() method. If
|
||
|
* <code>true</code> is returned, the exception is silently
|
||
|
* ignored and the converter moves onto the next frame.
|
||
|
*/
|
||
|
public boolean converterException(Throwable t);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Implementation of <code>ProgressListener</code> that writes
|
||
|
* notification text to a <code>PrintWriter</code>.
|
||
|
*/
|
||
|
// REVIEW: i18n of text and order required.
|
||
|
static public class PrintWriterProgressListener implements ProgressListener
|
||
|
{
|
||
|
static public final int NO_DETAIL = 0;
|
||
|
|
||
|
/**
|
||
|
* Level of detail typically expected of expert
|
||
|
* users.
|
||
|
*/
|
||
|
static public final int EXPERT_DETAIL = 1;
|
||
|
|
||
|
/**
|
||
|
* Verbose detail.
|
||
|
*/
|
||
|
static public final int VERBOSE_DETAIL = 2;
|
||
|
|
||
|
/**
|
||
|
* Debug detail. All frame read notifications are shown.
|
||
|
*/
|
||
|
static public final int DEBUG_DETAIL = 7;
|
||
|
|
||
|
static public final int MAX_DETAIL = 10;
|
||
|
|
||
|
private PrintWriter pw;
|
||
|
|
||
|
private int detailLevel;
|
||
|
|
||
|
static public PrintWriterProgressListener newStdOut(int detail)
|
||
|
{
|
||
|
return new PrintWriterProgressListener(
|
||
|
new PrintWriter(System.out, true), detail);
|
||
|
}
|
||
|
|
||
|
public PrintWriterProgressListener(PrintWriter writer, int detailLevel)
|
||
|
{
|
||
|
this.pw = writer;
|
||
|
this.detailLevel = detailLevel;
|
||
|
}
|
||
|
|
||
|
|
||
|
public boolean isDetail(int detail)
|
||
|
{
|
||
|
return (this.detailLevel >= detail);
|
||
|
}
|
||
|
|
||
|
public void converterUpdate(int updateID, int param1, int param2)
|
||
|
{
|
||
|
if (isDetail(VERBOSE_DETAIL))
|
||
|
{
|
||
|
switch (updateID)
|
||
|
{
|
||
|
case UPDATE_CONVERT_COMPLETE:
|
||
|
// catch divide by zero errors.
|
||
|
if (param2==0)
|
||
|
param2 = 1;
|
||
|
|
||
|
pw.println();
|
||
|
pw.println("Converted "+param2+" frames in "+param1+" ms ("+
|
||
|
(param1/param2)+" ms per frame.)");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void parsedFrame(int frameNo, Header header)
|
||
|
{
|
||
|
if ((frameNo==0) && isDetail(VERBOSE_DETAIL))
|
||
|
{
|
||
|
String headerString = header.toString();
|
||
|
pw.println("File is a "+headerString);
|
||
|
}
|
||
|
else if (isDetail(MAX_DETAIL))
|
||
|
{
|
||
|
String headerString = header.toString();
|
||
|
pw.println("Prased frame "+frameNo+": "+headerString);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void readFrame(int frameNo, Header header)
|
||
|
{
|
||
|
if ((frameNo==0) && isDetail(VERBOSE_DETAIL))
|
||
|
{
|
||
|
String headerString = header.toString();
|
||
|
pw.println("File is a "+headerString);
|
||
|
}
|
||
|
else if (isDetail(MAX_DETAIL))
|
||
|
{
|
||
|
String headerString = header.toString();
|
||
|
pw.println("Read frame "+frameNo+": "+headerString);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void decodedFrame(int frameNo, Header header, Obuffer o)
|
||
|
{
|
||
|
if (isDetail(MAX_DETAIL))
|
||
|
{
|
||
|
String headerString = header.toString();
|
||
|
pw.println("Decoded frame "+frameNo+": "+headerString);
|
||
|
pw.println("Output: "+o);
|
||
|
}
|
||
|
else if (isDetail(VERBOSE_DETAIL))
|
||
|
{
|
||
|
if (frameNo==0)
|
||
|
{
|
||
|
pw.print("Converting.");
|
||
|
pw.flush();
|
||
|
}
|
||
|
|
||
|
if ((frameNo % 10)==0)
|
||
|
{
|
||
|
pw.print('.');
|
||
|
pw.flush();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public boolean converterException(Throwable t)
|
||
|
{
|
||
|
if (this.detailLevel>NO_DETAIL)
|
||
|
{
|
||
|
t.printStackTrace(pw);
|
||
|
pw.flush();
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|