From 4bd73458ec90983b7c7096ece6015fabcf8deb7f Mon Sep 17 00:00:00 2001 From: Narayan Kamath Date: Tue, 16 Jun 2015 20:26:07 +0100 Subject: Reimplement MicrophoneInputStream in terms of AudioRecord. Lets us delete the defunct MicrophoneInputStream class from the frameworks. bug: 21855957 (cherry picked from commit 44568997f88595aa59dac61e61b1e8f9b326d7de) Change-Id: I8a8719c5679b580b61f77e8ecc4029a7e2e885b8 --- .../speechrecorder/MicrophoneInputStream.java | 98 ++++++++ .../speechrecorder/SpeechRecorderActivity.java | 5 +- src/com/android/speechrecorder/WaveHeader.java | 276 +++++++++++++++++++++ 3 files changed, 375 insertions(+), 4 deletions(-) create mode 100644 src/com/android/speechrecorder/MicrophoneInputStream.java create mode 100644 src/com/android/speechrecorder/WaveHeader.java diff --git a/src/com/android/speechrecorder/MicrophoneInputStream.java b/src/com/android/speechrecorder/MicrophoneInputStream.java new file mode 100644 index 0000000..ab1f475 --- /dev/null +++ b/src/com/android/speechrecorder/MicrophoneInputStream.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 com.android.speechrecorder; + +import java.io.IOException; +import java.io.InputStream; + +import android.media.AudioRecord; + +import static android.media.AudioFormat.CHANNEL_IN_MONO; +import static android.media.AudioFormat.ENCODING_PCM_16BIT; +import static android.media.MediaRecorder.AudioSource.VOICE_RECOGNITION; + +/** + * Provides an InputStream like API over {@code android.media.AudioRecord}. + * + * Not thread safe. + */ +public final class MicrophoneInputStream extends InputStream { + + private final int mSampleRate; + private final int mBufferSize; + private final AudioRecord mAudioRecord; + + private boolean mRecording; + + public MicrophoneInputStream(int sampleRate) throws IOException { + mSampleRate = sampleRate; + mBufferSize = AudioRecord.getMinBufferSize(sampleRate, + CHANNEL_IN_MONO, ENCODING_PCM_16BIT); + mAudioRecord = createAudioRecord(); + mRecording = false; + } + + private AudioRecord createAudioRecord() throws IOException { + AudioRecord ar = new AudioRecord(VOICE_RECOGNITION, + mSampleRate, CHANNEL_IN_MONO, ENCODING_PCM_16BIT, mBufferSize); + + if (ar.getState() != AudioRecord.STATE_INITIALIZED) { + ar.release(); + throw new IOException("Unable to create AudioRecord"); + } + + return ar; + } + + private void maybeStartRecording() throws IOException { + if (mRecording) { + return; + } + + mAudioRecord.startRecording(); + + int recordingState = mAudioRecord.getRecordingState(); + if (recordingState != AudioRecord.RECORDSTATE_RECORDING) { + throw new IOException("Unexpected recordingState: " + recordingState); + } + mRecording = true; + } + + @Override + public int read() throws IOException { + throw new UnsupportedOperationException("Single byte read."); + } + + @Override + public int read(byte[] b, int offset, int length) throws IOException { + maybeStartRecording(); + + final int ret = mAudioRecord.read(b, offset, length); + if (ret == AudioRecord.ERROR_INVALID_OPERATION || + ret == AudioRecord.ERROR_BAD_VALUE) { + throw new IOException("AudioRecord.read returned: " + ret); + } + + return ret; + } + + @Override + public void close() throws IOException { + mAudioRecord.stop(); + mAudioRecord.release(); + } +} diff --git a/src/com/android/speechrecorder/SpeechRecorderActivity.java b/src/com/android/speechrecorder/SpeechRecorderActivity.java index 3ed77f2..6bedf69 100644 --- a/src/com/android/speechrecorder/SpeechRecorderActivity.java +++ b/src/com/android/speechrecorder/SpeechRecorderActivity.java @@ -19,9 +19,6 @@ package com.android.speechrecorder; import android.app.Activity; import android.os.Bundle; import android.os.Handler; -import android.speech.srec.Recognizer; -import android.speech.srec.WaveHeader; -import android.speech.srec.MicrophoneInputStream; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; @@ -155,7 +152,7 @@ public class SpeechRecorderActivity extends Activity { 11025; mBaos = new ByteArrayOutputStream(mSampleRate * 2 * 20); try { - mMicrophone = new MicrophoneInputStream(mSampleRate, mSampleRate * 15); + mMicrophone = new MicrophoneInputStream(mSampleRate); // mMicrophone = logInputStream(mUtterance.toString(), mMicrophone, mSampleRate); } catch (IOException e) { diff --git a/src/com/android/speechrecorder/WaveHeader.java b/src/com/android/speechrecorder/WaveHeader.java new file mode 100644 index 0000000..1d0a19d --- /dev/null +++ b/src/com/android/speechrecorder/WaveHeader.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * 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 com.android.speechrecorder; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * This class represents the header of a WAVE format audio file, which usually + * have a .wav suffix. The following integer valued fields are contained: + * + * + * Not yet ready to be supported, so + * @hide + */ +public class WaveHeader { + + // follows WAVE format in http://ccrma.stanford.edu/courses/422/projects/WaveFormat + + private static final String TAG = "WaveHeader"; + + private static final int HEADER_LENGTH = 44; + + /** Indicates PCM format. */ + public static final short FORMAT_PCM = 1; + /** Indicates ALAW format. */ + public static final short FORMAT_ALAW = 6; + /** Indicates ULAW format. */ + public static final short FORMAT_ULAW = 7; + + private short mFormat; + private short mNumChannels; + private int mSampleRate; + private short mBitsPerSample; + private int mNumBytes; + + /** + * Construct a WaveHeader, with all fields defaulting to zero. + */ + public WaveHeader() { + } + + /** + * Construct a WaveHeader, with fields initialized. + * @param format format of audio data, + * one of {@link #FORMAT_PCM}, {@link #FORMAT_ULAW}, or {@link #FORMAT_ALAW}. + * @param numChannels 1 for mono, 2 for stereo. + * @param sampleRate typically 8000, 11025, 16000, 22050, or 44100 hz. + * @param bitsPerSample usually 16 for PCM, 8 for ULAW or 8 for ALAW. + * @param numBytes size of audio data after this header, in bytes. + */ + public WaveHeader(short format, short numChannels, int sampleRate, short bitsPerSample, int numBytes) { + mFormat = format; + mSampleRate = sampleRate; + mNumChannels = numChannels; + mBitsPerSample = bitsPerSample; + mNumBytes = numBytes; + } + + /** + * Get the format field. + * @return format field, + * one of {@link #FORMAT_PCM}, {@link #FORMAT_ULAW}, or {@link #FORMAT_ALAW}. + */ + public short getFormat() { + return mFormat; + } + + /** + * Set the format field. + * @param format + * one of {@link #FORMAT_PCM}, {@link #FORMAT_ULAW}, or {@link #FORMAT_ALAW}. + * @return reference to this WaveHeader instance. + */ + public WaveHeader setFormat(short format) { + mFormat = format; + return this; + } + + /** + * Get the number of channels. + * @return number of channels, 1 for mono, 2 for stereo. + */ + public short getNumChannels() { + return mNumChannels; + } + + /** + * Set the number of channels. + * @param numChannels 1 for mono, 2 for stereo. + * @return reference to this WaveHeader instance. + */ + public WaveHeader setNumChannels(short numChannels) { + mNumChannels = numChannels; + return this; + } + + /** + * Get the sample rate. + * @return sample rate, typically 8000, 11025, 16000, 22050, or 44100 hz. + */ + public int getSampleRate() { + return mSampleRate; + } + + /** + * Set the sample rate. + * @param sampleRate sample rate, typically 8000, 11025, 16000, 22050, or 44100 hz. + * @return reference to this WaveHeader instance. + */ + public WaveHeader setSampleRate(int sampleRate) { + mSampleRate = sampleRate; + return this; + } + + /** + * Get the number of bits per sample. + * @return number of bits per sample, + * usually 16 for PCM, 8 for ULAW or 8 for ALAW. + */ + public short getBitsPerSample() { + return mBitsPerSample; + } + + /** + * Set the number of bits per sample. + * @param bitsPerSample number of bits per sample, + * usually 16 for PCM, 8 for ULAW or 8 for ALAW. + * @return reference to this WaveHeader instance. + */ + public WaveHeader setBitsPerSample(short bitsPerSample) { + mBitsPerSample = bitsPerSample; + return this; + } + + /** + * Get the size of audio data after this header, in bytes. + * @return size of audio data after this header, in bytes. + */ + public int getNumBytes() { + return mNumBytes; + } + + /** + * Set the size of audio data after this header, in bytes. + * @param numBytes size of audio data after this header, in bytes. + * @return reference to this WaveHeader instance. + */ + public WaveHeader setNumBytes(int numBytes) { + mNumBytes = numBytes; + return this; + } + + /** + * Read and initialize a WaveHeader. + * @param in {@link java.io.InputStream} to read from. + * @return number of bytes consumed. + * @throws IOException + */ + public int read(InputStream in) throws IOException { + /* RIFF header */ + readId(in, "RIFF"); + int numBytes = readInt(in) - 36; + readId(in, "WAVE"); + + /* fmt chunk */ + readId(in, "fmt "); + if (16 != readInt(in)) throw new IOException("fmt chunk length not 16"); + mFormat = readShort(in); + mNumChannels = readShort(in); + mSampleRate = readInt(in); + int byteRate = readInt(in); + short blockAlign = readShort(in); + mBitsPerSample = readShort(in); + if (byteRate != mNumChannels * mSampleRate * mBitsPerSample / 8) { + throw new IOException("fmt.ByteRate field inconsistent"); + } + if (blockAlign != mNumChannels * mBitsPerSample / 8) { + throw new IOException("fmt.BlockAlign field inconsistent"); + } + + /* data chunk */ + readId(in, "data"); + mNumBytes = readInt(in); + + return HEADER_LENGTH; + } + + private static void readId(InputStream in, String id) throws IOException { + for (int i = 0; i < id.length(); i++) { + if (id.charAt(i) != in.read()) throw new IOException( id + " tag not present"); + } + } + + private static int readInt(InputStream in) throws IOException { + return in.read() | (in.read() << 8) | (in.read() << 16) | (in.read() << 24); + } + + private static short readShort(InputStream in) throws IOException { + return (short)(in.read() | (in.read() << 8)); + } + + /** + * Write a WAVE file header. + * @param out {@link java.io.OutputStream} to receive the header. + * @return number of bytes written. + * @throws IOException + */ + public int write(OutputStream out) throws IOException { + /* RIFF header */ + writeId(out, "RIFF"); + writeInt(out, 36 + mNumBytes); + writeId(out, "WAVE"); + + /* fmt chunk */ + writeId(out, "fmt "); + writeInt(out, 16); + writeShort(out, mFormat); + writeShort(out, mNumChannels); + writeInt(out, mSampleRate); + writeInt(out, mNumChannels * mSampleRate * mBitsPerSample / 8); + writeShort(out, (short)(mNumChannels * mBitsPerSample / 8)); + writeShort(out, mBitsPerSample); + + /* data chunk */ + writeId(out, "data"); + writeInt(out, mNumBytes); + + return HEADER_LENGTH; + } + + private static void writeId(OutputStream out, String id) throws IOException { + for (int i = 0; i < id.length(); i++) out.write(id.charAt(i)); + } + + private static void writeInt(OutputStream out, int val) throws IOException { + out.write(val >> 0); + out.write(val >> 8); + out.write(val >> 16); + out.write(val >> 24); + } + + private static void writeShort(OutputStream out, short val) throws IOException { + out.write(val >> 0); + out.write(val >> 8); + } + + @Override + public String toString() { + return String.format( + "WaveHeader format=%d numChannels=%d sampleRate=%d bitsPerSample=%d numBytes=%d", + mFormat, mNumChannels, mSampleRate, mBitsPerSample, mNumBytes); + } + +} -- cgit v1.2.3