package edu.cmu.sphinx.frontend.util; import edu.cmu.sphinx.frontend.*; import edu.cmu.sphinx.frontend.endpoint.SpeechEndSignal; import edu.cmu.sphinx.frontend.endpoint.SpeechStartSignal; import edu.cmu.sphinx.util.props.*; import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import java.io.*; /** * Stores audio data into numbered (MS-)wav files. * TODO: currently the WavWriter buffers all audio data until a DataEndSignal occurs. * * @author Holger Brandl */ public class WavWriter extends BaseDataProcessor { /** * The pathname which must obey the pattern: pattern + i + .wav. After each DataEndSignal the * smallest unused 'i' is determined. Pattern is padded to create result file with fixed name * lenght. */ @S4String(defaultValue = "seg000000") public static final String PROP_OUT_FILE_NAME_PATTERN = "outFilePattern"; @S4Boolean(defaultValue = false) public static final String PROP_IS_COMPLETE_PATH = "isCompletePath"; /** 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 signed. */ @S4Boolean(defaultValue = true) public static final String PROP_SIGNED_DATA = "signedData"; /** The property specifying whether the input data is signed. */ @S4Boolean(defaultValue = false) public static final String PROP_CAPTURE_UTTERANCES = "captureUtterances"; private ByteArrayOutputStream baos; private DataOutputStream dos; private int sampleRate; private boolean isInSpeech; private boolean isSigned = true; private int bitsPerSample; private String outFileNamePattern; protected boolean captureUtts; private boolean isCompletePath; public WavWriter(String dumpFilePath, boolean isCompletePath, int bitsPerSample, boolean isSigned, boolean captureUtts) { initLogger(); this.outFileNamePattern = dumpFilePath; this.isCompletePath = isCompletePath; this.bitsPerSample = bitsPerSample; if (bitsPerSample % 8 != 0) { throw new Error("StreamDataSource: bits per sample must be a multiple of 8."); } this.isSigned = isSigned; this.captureUtts = captureUtts; initialize(); } public WavWriter() { } /* * @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); outFileNamePattern = ps.getString(WavWriter.PROP_OUT_FILE_NAME_PATTERN); isCompletePath = ps.getBoolean(PROP_IS_COMPLETE_PATH); bitsPerSample = ps.getInt(PROP_BITS_PER_SAMPLE); if (bitsPerSample % 8 != 0) { throw new Error("StreamDataSource: bits per sample must be a multiple of 8."); } isSigned = ps.getBoolean(PROP_SIGNED_DATA); captureUtts = ps.getBoolean(PROP_CAPTURE_UTTERANCES); initialize(); } @Override public Data getData() throws DataProcessingException { Data data = getPredecessor().getData(); if (data instanceof DataStartSignal) sampleRate = ((DataStartSignal) data).getSampleRate(); if (data instanceof DataStartSignal || (data instanceof SpeechStartSignal && captureUtts)) { baos = new ByteArrayOutputStream(); dos = new DataOutputStream(baos); } if ((data instanceof DataEndSignal && !captureUtts) || (data instanceof SpeechEndSignal && captureUtts)) { String wavName; if (isCompletePath) wavName = outFileNamePattern; else wavName = getNextFreeIndex(outFileNamePattern); writeFile(wavName); isInSpeech = false; } if (data instanceof SpeechStartSignal) isInSpeech = true; if ((data instanceof DoubleData || data instanceof FloatData) && (isInSpeech || !captureUtts)) { DoubleData dd = data instanceof DoubleData ? (DoubleData) data : DataUtil.FloatData2DoubleData((FloatData) data); double[] values = dd.getValues(); for (double value : values) { try { dos.writeShort(new Short((short) value)); } catch (IOException e) { e.printStackTrace(); } } } return data; } private static String getNextFreeIndex(String outPattern) { int fileIndex = 0; String fileName; while (true) { String indexString = Integer.toString(fileIndex); fileName = outPattern.substring(0, Math.max(0, outPattern.length() - indexString.length())) + indexString + ".wav"; if (!new File(fileName).isFile()) break; fileIndex++; } return fileName; } /** Initializes this DataProcessor. This is typically called after the DataProcessor has been configured. */ @Override public void initialize() { super.initialize(); assert outFileNamePattern != null; baos = new ByteArrayOutputStream(); } /** * Sets the pattern for the output file name. Useful to change the output * beside the properties * * @param outFileNamePattern file name */ public void setOutFilePattern (String outFileNamePattern) { this.outFileNamePattern = outFileNamePattern; } private static AudioFileFormat.Type getTargetType(String extension) { AudioFileFormat.Type[] typesSupported = AudioSystem.getAudioFileTypes(); for (AudioFileFormat.Type aTypesSupported : typesSupported) { if (aTypesSupported.getExtension().equals(extension)) { return aTypesSupported; } } return null; } /** * Converts a big-endian byte array into an array of doubles. Each consecutive bytes in the byte array are converted * into a double, and becomes the next element in the double array. The size of the returned array is * (length/bytesPerValue). Currently, only 1 byte (8-bit) or 2 bytes (16-bit) samples are supported. * * @param values source values * @param bytesPerValue the number of bytes per value * @param signedData whether the data is signed * @return a double array, or <code>null</code> if byteArray is of zero length * @throws ArrayIndexOutOfBoundsException if boundary fails */ public static byte[] valuesToBytes(double[] values, int bytesPerValue, boolean signedData) throws ArrayIndexOutOfBoundsException { byte[] byteArray = new byte[bytesPerValue * values.length]; int byteArInd = 0; for (double value : values) { int val = (int) value; for (int j = bytesPerValue - 1; j >= 0; j++) { byteArray[byteArInd + j] = (byte) (val & 0xff); val = val >> 8; } byteArInd += bytesPerValue; } return byteArray; } /** * Writes the current stream to disc; override this method if you want to take * additional action on file writes * * @param wavName name of the file to be written */ protected void writeFile(String wavName) { AudioFormat wavFormat = new AudioFormat(sampleRate, bitsPerSample, 1, isSigned, true); AudioFileFormat.Type outputType = getTargetType("wav"); byte[] abAudioData = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(abAudioData); AudioInputStream ais = new AudioInputStream(bais, wavFormat, abAudioData.length / wavFormat.getFrameSize()); File outWavFile = new File(wavName); if (AudioSystem.isFileTypeSupported(outputType, ais)) { try { AudioSystem.write(ais, outputType, outWavFile); } catch (IOException e) { e.printStackTrace(); } } } }