Created
August 21, 2013 08:07
-
-
Save westlinkin/6291622 to your computer and use it in GitHub Desktop.
record audio and save as .wav
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.ihanghai.googlesearch | |
import java.io.File; | |
import java.io.IOException; | |
import java.io.RandomAccessFile; | |
import android.media.AudioFormat; | |
import android.media.AudioRecord; | |
import android.media.MediaRecorder; | |
import android.media.MediaRecorder.AudioSource; | |
import android.util.Log; | |
public class ExtAudioRecorder | |
{ | |
private final static int[] sampleRates = {44100, 22050, 11025, 8000}; | |
public static ExtAudioRecorder getInstanse(Boolean recordingCompressed) | |
{ | |
ExtAudioRecorder result = null; | |
if(recordingCompressed) | |
{ | |
result = new ExtAudioRecorder( false, | |
AudioSource.MIC, | |
sampleRates[3], | |
AudioFormat.CHANNEL_CONFIGURATION_MONO, | |
AudioFormat.ENCODING_PCM_16BIT); | |
} | |
else | |
{ | |
int i=3; | |
do | |
{ | |
result = new ExtAudioRecorder( true, | |
AudioSource.MIC, | |
sampleRates[i], | |
AudioFormat.CHANNEL_CONFIGURATION_MONO, | |
AudioFormat.ENCODING_PCM_16BIT); | |
} while((++i<sampleRates.length) & !(result.getState() == ExtAudioRecorder.State.INITIALIZING)); | |
} | |
return result; | |
} | |
/** | |
* INITIALIZING : recorder is initializing; | |
* READY : recorder has been initialized, recorder not yet started | |
* RECORDING : recording | |
* ERROR : reconstruction needed | |
* STOPPED: reset needed | |
*/ | |
public enum State {INITIALIZING, READY, RECORDING, ERROR, STOPPED}; | |
public static final boolean RECORDING_UNCOMPRESSED = true; | |
public static final boolean RECORDING_COMPRESSED = false; | |
// The interval in which the recorded samples are output to the file | |
// Used only in uncompressed mode | |
private static final int TIMER_INTERVAL = 120; | |
// Toggles uncompressed recording on/off; RECORDING_UNCOMPRESSED / RECORDING_COMPRESSED | |
private boolean rUncompressed; | |
// Recorder used for uncompressed recording | |
private AudioRecord audioRecorder = null; | |
// Recorder used for compressed recording | |
private MediaRecorder mediaRecorder = null; | |
// Stores current amplitude (only in uncompressed mode) | |
private int cAmplitude= 0; | |
// Output file path | |
private String filePath = null; | |
// Recorder state; see State | |
private State state; | |
// File writer (only in uncompressed mode) | |
private RandomAccessFile randomAccessWriter; | |
// Number of channels, sample rate, sample size(size in bits), buffer size, audio source, sample size(see AudioFormat) | |
private short nChannels; | |
private int sRate; | |
private short bSamples; | |
private int bufferSize; | |
private int aSource; | |
private int aFormat; | |
// Number of frames written to file on each output(only in uncompressed mode) | |
private int framePeriod; | |
// Buffer for output(only in uncompressed mode) | |
private byte[] buffer; | |
// Number of bytes written to file after header(only in uncompressed mode) | |
// after stop() is called, this size is written to the header/data chunk in the wave file | |
private int payloadSize; | |
/** | |
* | |
* Returns the state of the recorder in a RehearsalAudioRecord.State typed object. | |
* Useful, as no exceptions are thrown. | |
* | |
* @return recorder state | |
*/ | |
public State getState() | |
{ | |
return state; | |
} | |
/* | |
* | |
* Method used for recording. | |
* | |
*/ | |
private AudioRecord.OnRecordPositionUpdateListener updateListener = new AudioRecord.OnRecordPositionUpdateListener() | |
{ | |
public void onPeriodicNotification(AudioRecord recorder) | |
{ | |
audioRecorder.read(buffer, 0, buffer.length); // Fill buffer | |
try | |
{ | |
randomAccessWriter.write(buffer); // Write buffer to file | |
payloadSize += buffer.length; | |
if (bSamples == 16) | |
{ | |
for (int i=0; i<buffer.length/2; i++) | |
{ // 16bit sample size | |
short curSample = getShort(buffer[i*2], buffer[i*2+1]); | |
if (curSample > cAmplitude) | |
{ // Check amplitude | |
cAmplitude = curSample; | |
} | |
} | |
} | |
else | |
{ // 8bit sample size | |
for (int i=0; i<buffer.length; i++) | |
{ | |
if (buffer[i] > cAmplitude) | |
{ // Check amplitude | |
cAmplitude = buffer[i]; | |
} | |
} | |
} | |
} | |
catch (IOException e) | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), "Error occured in updateListener, recording is aborted"); | |
//stop(); | |
} | |
} | |
public void onMarkerReached(AudioRecord recorder) | |
{ | |
// NOT USED | |
} | |
}; | |
/** | |
* | |
* | |
* Default constructor | |
* | |
* Instantiates a new recorder, in case of compressed recording the parameters can be left as 0. | |
* In case of errors, no exception is thrown, but the state is set to ERROR | |
* | |
*/ | |
public ExtAudioRecorder(boolean uncompressed, int audioSource, int sampleRate, int channelConfig, int audioFormat) | |
{ | |
try | |
{ | |
rUncompressed = uncompressed; | |
if (rUncompressed) | |
{ // RECORDING_UNCOMPRESSED | |
if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) | |
{ | |
bSamples = 16; | |
} | |
else | |
{ | |
bSamples = 8; | |
} | |
if (channelConfig == AudioFormat.CHANNEL_CONFIGURATION_MONO) | |
{ | |
nChannels = 1; | |
} | |
else | |
{ | |
nChannels = 2; | |
} | |
aSource = audioSource; | |
sRate = sampleRate; | |
aFormat = audioFormat; | |
framePeriod = sampleRate * TIMER_INTERVAL / 1000; | |
bufferSize = framePeriod * 2 * bSamples * nChannels / 8; | |
if (bufferSize < AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)) | |
{ // Check to make sure buffer size is not smaller than the smallest allowed one | |
bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat); | |
// Set frame period and timer interval accordingly | |
framePeriod = bufferSize / ( 2 * bSamples * nChannels / 8 ); | |
Log.w(ExtAudioRecorder.class.getName(), "Increasing buffer size to " + Integer.toString(bufferSize)); | |
} | |
audioRecorder = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat, bufferSize); | |
if (audioRecorder.getState() != AudioRecord.STATE_INITIALIZED) | |
throw new Exception("AudioRecord initialization failed"); | |
audioRecorder.setRecordPositionUpdateListener(updateListener); | |
audioRecorder.setPositionNotificationPeriod(framePeriod); | |
} else | |
{ // RECORDING_COMPRESSED | |
mediaRecorder = new MediaRecorder(); | |
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); | |
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); | |
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); | |
} | |
cAmplitude = 0; | |
filePath = null; | |
state = State.INITIALIZING; | |
} catch (Exception e) | |
{ | |
if (e.getMessage() != null) | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), e.getMessage()); | |
} | |
else | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), "Unknown error occured while initializing recording"); | |
} | |
state = State.ERROR; | |
} | |
} | |
/** | |
* Sets output file path, call directly after construction/reset. | |
* | |
* @param output file path | |
* | |
*/ | |
public void setOutputFile(String argPath) | |
{ | |
try | |
{ | |
if (state == State.INITIALIZING) | |
{ | |
filePath = argPath; | |
if (!rUncompressed) | |
{ | |
mediaRecorder.setOutputFile(filePath); | |
} | |
} | |
} | |
catch (Exception e) | |
{ | |
if (e.getMessage() != null) | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), e.getMessage()); | |
} | |
else | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), "Unknown error occured while setting output path"); | |
} | |
state = State.ERROR; | |
} | |
} | |
/** | |
* | |
* Returns the largest amplitude sampled since the last call to this method. | |
* | |
* @return returns the largest amplitude since the last call, or 0 when not in recording state. | |
* | |
*/ | |
public int getMaxAmplitude() | |
{ | |
if (state == State.RECORDING) | |
{ | |
if (rUncompressed) | |
{ | |
int result = cAmplitude; | |
cAmplitude = 0; | |
return result; | |
} | |
else | |
{ | |
try | |
{ | |
return mediaRecorder.getMaxAmplitude(); | |
} | |
catch (IllegalStateException e) | |
{ | |
return 0; | |
} | |
} | |
} | |
else | |
{ | |
return 0; | |
} | |
} | |
/** | |
* | |
* Prepares the recorder for recording, in case the recorder is not in the INITIALIZING state and the file path was not set | |
* the recorder is set to the ERROR state, which makes a reconstruction necessary. | |
* In case uncompressed recording is toggled, the header of the wave file is written. | |
* In case of an exception, the state is changed to ERROR | |
* | |
*/ | |
public void prepare() | |
{ | |
try | |
{ | |
if (state == State.INITIALIZING) | |
{ | |
if (rUncompressed) | |
{ | |
if ((audioRecorder.getState() == AudioRecord.STATE_INITIALIZED) & (filePath != null)) | |
{ | |
// write file header | |
randomAccessWriter = new RandomAccessFile(filePath, "rw"); | |
randomAccessWriter.setLength(0); // Set file length to 0, to prevent unexpected behavior in case the file already existed | |
randomAccessWriter.writeBytes("RIFF"); | |
randomAccessWriter.writeInt(0); // Final file size not known yet, write 0 | |
randomAccessWriter.writeBytes("WAVE"); | |
randomAccessWriter.writeBytes("fmt "); | |
randomAccessWriter.writeInt(Integer.reverseBytes(16)); // Sub-chunk size, 16 for PCM | |
randomAccessWriter.writeShort(Short.reverseBytes((short) 1)); // AudioFormat, 1 for PCM | |
randomAccessWriter.writeShort(Short.reverseBytes(nChannels));// Number of channels, 1 for mono, 2 for stereo | |
randomAccessWriter.writeInt(Integer.reverseBytes(sRate)); // Sample rate | |
randomAccessWriter.writeInt(Integer.reverseBytes(sRate*bSamples*nChannels/8)); // Byte rate, SampleRate*NumberOfChannels*BitsPerSample/8 | |
randomAccessWriter.writeShort(Short.reverseBytes((short)(nChannels*bSamples/8))); // Block align, NumberOfChannels*BitsPerSample/8 | |
randomAccessWriter.writeShort(Short.reverseBytes(bSamples)); // Bits per sample | |
randomAccessWriter.writeBytes("data"); | |
randomAccessWriter.writeInt(0); // Data chunk size not known yet, write 0 | |
buffer = new byte[framePeriod*bSamples/8*nChannels]; | |
state = State.READY; | |
} | |
else | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), "prepare() method called on uninitialized recorder"); | |
state = State.ERROR; | |
} | |
} | |
else | |
{ | |
mediaRecorder.prepare(); | |
state = State.READY; | |
} | |
} | |
else | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), "prepare() method called on illegal state"); | |
release(); | |
state = State.ERROR; | |
} | |
} | |
catch(Exception e) | |
{ | |
if (e.getMessage() != null) | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), e.getMessage()); | |
} | |
else | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), "Unknown error occured in prepare()"); | |
} | |
state = State.ERROR; | |
} | |
} | |
/** | |
* | |
* | |
* Releases the resources associated with this class, and removes the unnecessary files, when necessary | |
* | |
*/ | |
public void release() | |
{ | |
if (state == State.RECORDING) | |
{ | |
stop(); | |
} | |
else | |
{ | |
if ((state == State.READY) & (rUncompressed)) | |
{ | |
try | |
{ | |
randomAccessWriter.close(); // Remove prepared file | |
} | |
catch (IOException e) | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), "I/O exception occured while closing output file"); | |
} | |
(new File(filePath)).delete(); | |
} | |
} | |
if (rUncompressed) | |
{ | |
if (audioRecorder != null) | |
{ | |
audioRecorder.release(); | |
} | |
} | |
else | |
{ | |
if (mediaRecorder != null) | |
{ | |
mediaRecorder.release(); | |
} | |
} | |
} | |
/** | |
* | |
* | |
* Resets the recorder to the INITIALIZING state, as if it was just created. | |
* In case the class was in RECORDING state, the recording is stopped. | |
* In case of exceptions the class is set to the ERROR state. | |
* | |
*/ | |
public void reset() | |
{ | |
try | |
{ | |
if (state != State.ERROR) | |
{ | |
release(); | |
filePath = null; // Reset file path | |
cAmplitude = 0; // Reset amplitude | |
if (rUncompressed) | |
{ | |
audioRecorder = new AudioRecord(aSource, sRate, nChannels+1, aFormat, bufferSize); | |
} | |
else | |
{ | |
mediaRecorder = new MediaRecorder(); | |
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); | |
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); | |
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); | |
} | |
state = State.INITIALIZING; | |
} | |
} | |
catch (Exception e) | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), e.getMessage()); | |
state = State.ERROR; | |
} | |
} | |
/** | |
* | |
* | |
* Starts the recording, and sets the state to RECORDING. | |
* Call after prepare(). | |
* | |
*/ | |
public void start() | |
{ | |
if (state == State.READY) | |
{ | |
if (rUncompressed) | |
{ | |
payloadSize = 0; | |
audioRecorder.startRecording(); | |
audioRecorder.read(buffer, 0, buffer.length); | |
} | |
else | |
{ | |
mediaRecorder.start(); | |
} | |
state = State.RECORDING; | |
} | |
else | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), "start() called on illegal state"); | |
state = State.ERROR; | |
} | |
} | |
/** | |
* | |
* | |
* Stops the recording, and sets the state to STOPPED. | |
* In case of further usage, a reset is needed. | |
* Also finalizes the wave file in case of uncompressed recording. | |
* | |
*/ | |
public void stop() | |
{ | |
if (state == State.RECORDING) | |
{ | |
if (rUncompressed) | |
{ | |
audioRecorder.stop(); | |
try | |
{ | |
randomAccessWriter.seek(4); // Write size to RIFF header | |
randomAccessWriter.writeInt(Integer.reverseBytes(36+payloadSize)); | |
randomAccessWriter.seek(40); // Write size to Subchunk2Size field | |
randomAccessWriter.writeInt(Integer.reverseBytes(payloadSize)); | |
randomAccessWriter.close(); | |
} | |
catch(IOException e) | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), "I/O exception occured while closing output file"); | |
state = State.ERROR; | |
} | |
} | |
else | |
{ | |
mediaRecorder.stop(); | |
} | |
state = State.STOPPED; | |
} | |
else | |
{ | |
Log.e(ExtAudioRecorder.class.getName(), "stop() called on illegal state"); | |
state = State.ERROR; | |
} | |
} | |
/* | |
* | |
* Converts a byte[2] to a short, in LITTLE_ENDIAN format | |
* | |
*/ | |
private short getShort(byte argB1, byte argB2) | |
{ | |
return (short)(argB1 | (argB2 << 8)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//获取类的实例 | |
ExtAudioRecorder extAudioRecorder = ExtAudioRecorder.getInstanse(false); //未压缩的录音(WAV) | |
/** | |
* 录制wav格式文件 | |
* @param path : 文件路径 | |
*/ | |
public static File recordChat(ExtAudioRecorder extAudioRecorder,String savePath,String fileName) { | |
File dir = new File(savePath); | |
//如果该目录没有存在,则新建目录 | |
if(dir.list() == null){ | |
dir.mkdirs(); | |
} | |
//获取录音文件 | |
File file=new File(savePath+fileName); | |
//设置输出文件 | |
extAudioRecorder.setOutputFile(savePath+fileName); | |
extAudioRecorder.prepare(); | |
//开始录音 | |
extAudioRecorder.start(); | |
return file; | |
} | |
/** | |
* 停止录音 | |
* @param mediaRecorder 待停止的录音机 | |
* @return 返回 | |
*/ | |
public static void stopRecord(final ExtAudioRecorder extAudioRecorder){ | |
extAudioRecorder.stop(); | |
extAudioRecorder.release(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment