/*
* -----------------------------------------------------------------------
* File: $Source: /home/keith/cvsroot/projects/LanguageAids/uk/co/dabsol/stribley/sound/AudioFileExtractor.java,v $
* Version: $Revision: 852 $
* Last Modified: $Date: 2007-06-09 16:02:23 +0700 (Sat, 09 Jun 2007) $
* -----------------------------------------------------------------------
* Copyright (C) 2004 Keith Stribley <tech@thanlwinsoft.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
* -----------------------------------------------------------------------
*/
package org.thanlwinsoft.languagetest.sound;
import java.io.File;
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.SwingUtilities;
import org.tritonus.share.sampled.AudioUtils;
/**
*
* @author keith
*/
public class AudioFileExtractor implements Runnable
{
private ExtractionParameters parameters = null;
private AudioInputStream interimIS = null;
private AudioInputStream oldIS = null;
private Thread extractionThread = null;
private boolean run = true;
private final AudioFileExtractor.Listener listener;
/** Creates a new instance of AudioFileExtractor */
public AudioFileExtractor(AudioFileExtractor.Listener listener)
{
this.listener = listener;
}
/** Extracts a section of audio data from one file and stores it in another.
* @param oldFile Old audiofile
* @param newFile file name for extracted data
* @param startOffset millisec offset of start of data to extract
* @param endOffset millisec offset of end of data to extract
* @return length in millisec of data extracted
*/
synchronized public void extractFile(File oldFile, File newFile, long startOffset, long endOffset)
throws PreviousExtractionNotFinishedException, UnsupportedAudioFileException,
IOException
{
if (parameters != null) throw new PreviousExtractionNotFinishedException();
oldIS = AudioSystem.getAudioInputStream(oldFile);
AudioFormat.Encoding newEnc = Recorder.encodingFromExtension(newFile);
// go via PCM since it has well defined frame rates etc
if (oldIS.getFormat().getEncoding() == AudioFormat.Encoding.PCM_SIGNED)
{
interimIS = oldIS;
}
else if (AudioSystem.isConversionSupported
(AudioFormat.Encoding.PCM_SIGNED, oldIS.getFormat()))
{
interimIS = AudioSystem.getAudioInputStream
(AudioFormat.Encoding.PCM_SIGNED, oldIS);
}
else
{
throw new UnsupportedAudioFileException("Cannot convert " +
oldIS.getFormat().getEncoding() + " to " + newEnc);
}
long startByteOffset =
AudioUtils.millis2BytesFrameAligned(startOffset, interimIS.getFormat());
long byteLength = AudioSystem.NOT_SPECIFIED;
if (endOffset > 0)
{
byteLength = -startByteOffset +
AudioUtils.millis2BytesFrameAligned(endOffset, interimIS.getFormat());
}
parameters =
new AudioFileExtractor.ExtractionParameters(newFile, oldFile,
startByteOffset, byteLength);
if (extractionThread == null)
{
extractionThread = new Thread(this);
extractionThread.start();
}
}
public void close()
{
run = false;
}
public void run()
{
while (run)
{
if (parameters == null)
{
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
}
continue;
}
try
{
// seek to start of stream
byte [] readBuffer = new byte[1024];
long readPosition = 0;
File tempAudioFile = File.createTempFile("LangTestRec",".raw");
tempAudioFile.deleteOnExit();
OutputStream outputStream =
new BufferedOutputStream(new FileOutputStream(tempAudioFile));
long newLength = 0;
long read = 0;
int nullReadCount = 0;
while (read > -1)
{
long length = readBuffer.length;
// split read over start boundary to making writing easy
if (readPosition < parameters.getStartByteOffset() &&
readPosition + length > parameters.getStartByteOffset())
{
length = (int)(parameters.getStartByteOffset() - readPosition);
}
if (/*interimIS.markSupported() && */
readPosition < parameters.getStartByteOffset())
{
read = interimIS.skip(length);
}
else
{
read = interimIS.read(readBuffer, 0, (int)length);
}
if (read > -1)
{
if (readPosition >= parameters.getStartByteOffset())
{
// write out to buffer
outputStream.write(readBuffer, 0, (int)read);
newLength += read;
}
readPosition += read;
if (read > 0)
{
nullReadCount = 0; // reset null counter
final int progress = (int)
((100 * (float)readPosition) /
(float)(parameters.getStartByteOffset() +
parameters.getByteLength()));
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
listener.showProgress(progress);
}
});
}
else
{
nullReadCount++;
if (nullReadCount > 1000)
{
System.out.println("AudioFileExtractor empty reads aborting");
break;
}
try
{
Thread.sleep(10);
}
catch (InterruptedException e) {};
}
}
if (parameters.getByteLength() > 0 &&
readPosition >= parameters.getStartByteOffset() +
parameters.getByteLength())
{
break; // finished
}
}
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
listener.showProgress(-1);
}
});
// pad if newLength not whole number of frames
// the boundary may have been bigger than the file, but the
// current readPosition must be the smaller of the real data
// length and the end position requested
parameters.setByteLength(newLength);
long frameLength = newLength / interimIS.getFormat().getFrameSize();
long frameAlignedByteLength =
frameLength * interimIS.getFormat().getFrameSize();
if (frameAlignedByteLength != newLength)
{
// padd last frame to make it complete
frameAlignedByteLength += interimIS.getFormat().getFrameSize();
frameLength++;
while (frameAlignedByteLength > newLength)
{
outputStream.write('\0');
newLength++;
}
}
// now create a new AudioInputSteam from the old truncated as
// required
outputStream.close();
if (interimIS != oldIS)
{
interimIS.close();
}
oldIS.close();
InputStream rawIS =
new BufferedInputStream(new FileInputStream(tempAudioFile));
AudioInputStream extractedIS =
new AudioInputStream(rawIS, interimIS.getFormat(),
frameLength);
// finally convert to new output stream
AudioInputStream translatedIS =
AudioSystem.getAudioInputStream(
Recorder.encodingFromExtension(parameters.getNewFile()),
extractedIS);
OutputStream oStream =
new NullStripOutputStream(new FileOutputStream(parameters.getNewFile()));
int wrote =
AudioSystem.write(translatedIS,
Recorder.fileFormatFromExtension(parameters.getNewFile()),
oStream);
rawIS.close();
extractedIS.close();
translatedIS.close();
oStream.close();
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
listener.showProgress(100);
}
});
System.out.println("Extracted " + wrote + " uncompressed: " +
parameters.getByteLength());
// calculate extracted length
final long wroteMs =
AudioUtils.bytes2Millis(parameters.getByteLength(),
extractedIS.getFormat());
// now close the various is
translatedIS = null;
rawIS = null;
extractedIS = null;
oldIS = null;
interimIS = null;
// finished with temporary file so delete it now
tempAudioFile.delete();
// notify gui of finish
final File extractedFile = parameters.getNewFile();
// delete parameters before telling gui to extract next one
synchronized (this)
{
parameters = null;
}
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
listener.extractionFinished(extractedFile, wroteMs);
}
}
);
}
catch (FileNotFoundException e)
{
synchronized (this)
{
parameters = null;
}
System.out.println(e.getMessage());
final File extractedFile = parameters.getNewFile();
final String message = "File not found.";
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
listener.extractionFailed(extractedFile,
message);
}
});
}
catch (IOException e)
{
synchronized (this)
{
parameters = null;
}
System.out.println(e.getMessage());
final File extractedFile = parameters.getNewFile();
final String message = e.getLocalizedMessage();
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
listener.extractionFailed(extractedFile,
message);
}
});
}
}
}
class ExtractionParameters
{
private File oldFile;
private File newFile;
private long startByteOffset;
private long byteLength;
//private long extractedLength = AudioSystem.NOT_SPECIFIED;
ExtractionParameters(File newFile, File oldFile,
long startByteOffset, long byteLength)
{
this.oldFile = oldFile;
this.newFile = newFile;
this.startByteOffset = startByteOffset;
this.byteLength = byteLength;
}
public File getOldFile() { return oldFile; }
public File getNewFile() { return newFile; }
public long getStartByteOffset() { return startByteOffset; }
public long getByteLength() { return byteLength; }
//public void setExtractedLength(long length) { extractedLength = length;}
public void setByteLength(long newLength) { byteLength = newLength; }
}
public class PreviousExtractionNotFinishedException
extends java.lang.Exception
{
/**
*
*/
private static final long serialVersionUID = 8945418933344598071L;
PreviousExtractionNotFinishedException()
{
super();
}
}
public interface Listener
{
void showProgress(int percent);
void extractionFinished(File extractedFile, long msLengthExtracted);
void extractionFailed(File extractedFile, String reason);
}
}