/* * 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 audio.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 audio.javazoom.jl.decoder.Bitstream; import audio.javazoom.jl.decoder.Decoder; import audio.javazoom.jl.decoder.Header; import audio.javazoom.jl.decoder.JavaLayerException; import audio.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; } } }