/* * Copyright 1999-2002 Carnegie Mellon University. * Portions Copyright 2002 Sun Microsystems, Inc. * Portions Copyright 2002 Mitsubishi Electric Research Laboratories. * All Rights Reserved. Use is subject to license terms. * * See the file "license.terms" for information on usage and * redistribution of this file, and for a DISCLAIMER OF ALL * WARRANTIES. * */ package edu.cmu.sphinx.frontend.util; import java.io.IOException; import java.io.InputStream; import edu.cmu.sphinx.frontend.*; import edu.cmu.sphinx.util.TimeFrame; import edu.cmu.sphinx.util.props.*; /** * A StreamDataSource converts data from an InputStream into Data objects. One * would call {@link #setInputStream(InputStream,TimeFrame) setInputStream} to set * the input stream, and call {@link #getData} to obtain the Data object. The * InputStream can be an arbitrary stream, for example a data from the network * or from a pipe. * * StreamDataSource is not aware about incoming data format and assumes * that incoming data matches StreamDataSource configuration. By default it's configured * to read 16 kHz little-endian 16-bit signed raw data. If data has wrong format * the result of the recognition is undefined. Also note that the sample rate of the * data must match the sample required by the the acoustic model. If your * model decodes 16 kHz files you can't recognize 8kHz data using it. * * You can use AudioFileDataSource instead to read the file headers and * to convert incoming data to the required format automatically. */ public class StreamDataSource extends BaseDataProcessor { /** The property for the sample rate. */ @S4Integer(defaultValue = 16000) public static final String PROP_SAMPLE_RATE = "sampleRate"; /** * The property for the number of bytes to read from the InputStream each * time. */ @S4Integer(defaultValue = 3200) public static final String PROP_BYTES_PER_READ = "bytesPerRead"; /** The property for the number of bits per value. */ @S4Integer(defaultValue = 16) public static final String PROP_BITS_PER_SAMPLE = "bitsPerSample"; /** The property specifying whether the input data is big-endian. */ @S4Boolean(defaultValue = false) public static final String PROP_BIG_ENDIAN_DATA = "bigEndianData"; /** The property specifying whether the input data is signed. */ @S4Boolean(defaultValue = true) public static final String PROP_SIGNED_DATA = "signedData"; private InputStream dataStream; protected int sampleRate; private int bytesPerRead; private int bytesPerValue; private long totalValuesRead; private boolean bigEndian; private boolean signedData; private boolean streamEndReached; private boolean utteranceEndSent; private boolean utteranceStarted; protected int bitsPerSample; private TimeFrame timeFrame = TimeFrame.INFINITE; public StreamDataSource(int sampleRate, int bytesPerRead, int bitsPerSample, boolean bigEndian, boolean signedData) { initLogger(); init(sampleRate, bytesPerRead, bitsPerSample, bigEndian, signedData); } public StreamDataSource() { } /* * (non-Javadoc) * @see * edu.cmu.sphinx.util.props.Configurable#newProperties(edu.cmu.sphinx. * util.props.PropertySheet) */ @Override public void newProperties(PropertySheet ps) throws PropertyException { super.newProperties(ps); init( ps.getInt(PROP_SAMPLE_RATE), ps.getInt(PROP_BYTES_PER_READ), ps.getInt(PROP_BITS_PER_SAMPLE), ps.getBoolean(PROP_BIG_ENDIAN_DATA), ps.getBoolean(PROP_SIGNED_DATA)); } private void init(int sampleRate, int bytesPerRead, int bitsPerSample, boolean bigEndian, boolean signedData) { this.sampleRate = sampleRate; this.bytesPerRead = bytesPerRead; this.bitsPerSample = bitsPerSample; if (this.bitsPerSample % 8 != 0) throw new IllegalArgumentException( "bits per sample must be a multiple of 8"); this.bytesPerValue = bitsPerSample / 8; this.bigEndian = bigEndian; this.signedData = signedData; this.bytesPerRead += bytesPerRead % 2; } /* * (non-Javadoc) * @see * edu.cmu.sphinx.frontend.DataProcessor#initialize(edu.cmu.sphinx.frontend * .CommonConfig) */ @Override public void initialize() { super.initialize(); } public void setInputStream(InputStream inputStream) { setInputStream(inputStream, TimeFrame.INFINITE); } /** * Sets the InputStream from which this StreamDataSource reads. * * @param inputStream the InputStream from which audio data comes * @param timeFrame time frame to process */ public void setInputStream(InputStream inputStream, TimeFrame timeFrame) { dataStream = inputStream; this.timeFrame = timeFrame; streamEndReached = false; utteranceEndSent = false; utteranceStarted = false; totalValuesRead = 0; } /** * Reads and returns the next Data from the InputStream of * StreamDataSource, return null if no data is read and end of file is * reached. * * @return the next Data or <code>null</code> if none is available * @throws DataProcessingException if there is a data processing error */ @Override public Data getData() throws DataProcessingException { Data output = null; if (streamEndReached) { if (!utteranceEndSent) { // since 'firstSampleNumber' starts at 0, the last // sample number should be 'totalValuesRead - 1' output = new DataEndSignal(getDuration()); utteranceEndSent = true; } } else { if (!utteranceStarted) { utteranceStarted = true; output = new DataStartSignal(sampleRate); } else { if (dataStream != null) { do { output = readNextFrame(); } while (output != null && getDuration() < timeFrame.getStart()); if ((output == null || getDuration() > timeFrame.getEnd()) && !utteranceEndSent) { output = new DataEndSignal(getDuration()); utteranceEndSent = true; streamEndReached = true; } } else { logger.warning("Input stream is not set"); if (!utteranceEndSent) { output = new DataEndSignal(getDuration()); utteranceEndSent = true; } } } } return output; } /** * Returns the next Data from the input stream, or null if there is none * available * * @return a Data or null * @throws edu.cmu.sphinx.frontend.DataProcessingException */ private DoubleData readNextFrame() throws DataProcessingException { // read one frame's worth of bytes int read; int totalRead = 0; final int bytesToRead = bytesPerRead; byte[] samplesBuffer = new byte[bytesPerRead]; long firstSample = totalValuesRead; try { do { read = dataStream.read(samplesBuffer, totalRead, bytesToRead - totalRead); if (read > 0) { totalRead += read; } } while (read != -1 && totalRead < bytesToRead); if (totalRead <= 0) { closeDataStream(); return null; } // shrink incomplete frames totalValuesRead += (totalRead / bytesPerValue); if (totalRead < bytesToRead) { totalRead = (totalRead % 2 == 0) ? totalRead + 2 : totalRead + 3; byte[] shrinkedBuffer = new byte[totalRead]; System .arraycopy(samplesBuffer, 0, shrinkedBuffer, 0, totalRead); samplesBuffer = shrinkedBuffer; closeDataStream(); } } catch (IOException ioe) { throw new DataProcessingException("Error reading data", ioe); } // turn it into an Data object double[] doubleData; if (bigEndian) { doubleData = DataUtil.bytesToValues(samplesBuffer, 0, totalRead, bytesPerValue, signedData); } else { doubleData = DataUtil.littleEndianBytesToValues(samplesBuffer, 0, totalRead, bytesPerValue, signedData); } return new DoubleData(doubleData, sampleRate, firstSample); } private void closeDataStream() throws IOException { streamEndReached = true; if (dataStream != null) { dataStream.close(); } } /** * Returns the duration of the current data stream in milliseconds. * * @return the duration of the current data stream in milliseconds */ private long getDuration() { return (long) (((double) totalValuesRead / (double) sampleRate) * 1000.0); } }