/*---------------------------------------------------------------------------* * MicrophoneInputStream.java * * * * Copyright 2007 Nuance Communciations, Inc. * * * * Licensed under the Apache License, Version 2.0 (the 'License'); * * you may not use this file except in compliance with the License. * * * * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an 'AS IS' BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * * * *---------------------------------------------------------------------------*/ package android.speech.srec; import java.io.IOException; import java.io.InputStream; import java.lang.IllegalStateException; /** * PCM input stream from the microphone, 16 bits per sample. */ public final class MicrophoneInputStream extends InputStream { static { System.loadLibrary("srec_jni"); } private final static String TAG = "MicrophoneInputStream"; private int mAudioRecord = 0; private byte[] mOneByte = new byte[1]; /** * MicrophoneInputStream constructor. * @param sampleRate sample rate of the microphone, typically 11025 or 8000. * @param fifoDepth depth of the real time fifo, measured in sampleRate clock ticks. * This determines how long an application may delay before losing data. */ public MicrophoneInputStream(int sampleRate, int fifoDepth) throws IOException { mAudioRecord = AudioRecordNew(sampleRate, fifoDepth); if (mAudioRecord == 0) throw new IOException("AudioRecord constructor failed - busy?"); int status = AudioRecordStart(mAudioRecord); if (status != 0) { close(); throw new IOException("AudioRecord start failed: " + status); } } @Override public int read() throws IOException { if (mAudioRecord == 0) throw new IllegalStateException("not open"); int rtn = AudioRecordRead(mAudioRecord, mOneByte, 0, 1); return rtn == 1 ? ((int)mOneByte[0] & 0xff) : -1; } @Override public int read(byte[] b) throws IOException { if (mAudioRecord == 0) throw new IllegalStateException("not open"); return AudioRecordRead(mAudioRecord, b, 0, b.length); } @Override public int read(byte[] b, int offset, int length) throws IOException { if (mAudioRecord == 0) throw new IllegalStateException("not open"); // TODO: should we force all reads to be a multiple of the sample size? return AudioRecordRead(mAudioRecord, b, offset, length); } /** {@collect.stats} * {@description.open} * Closes this stream. * {@description.close} * * {@property.open runtime formal:java.io.Closeable_MultipleClose} * <p>Although only the first call has any effect, it is safe to call close * multiple times on the same object. This is more lenient than the * overridden {@code AutoCloseable.close()}, which may be called at most * once. * {@property.close} */ @Override public void close() throws IOException { if (mAudioRecord != 0) { try { AudioRecordStop(mAudioRecord); } finally { try { AudioRecordDelete(mAudioRecord); } finally { mAudioRecord = 0; } } } } @Override protected void finalize() throws Throwable { if (mAudioRecord != 0) { close(); throw new IOException("someone forgot to close MicrophoneInputStream"); } } // // AudioRecord JNI interface // private static native int AudioRecordNew(int sampleRate, int fifoDepth); private static native int AudioRecordStart(int audioRecord); private static native int AudioRecordRead(int audioRecord, byte[] b, int offset, int length) throws IOException; private static native void AudioRecordStop(int audioRecord) throws IOException; private static native void AudioRecordDelete(int audioRecord) throws IOException; }