Android录音、WAV、AMR
2016-06-04 19:14
615 查看
简单介绍
Android提供了两个API用于实现录音功能:android.media.AudioRecord、android.media.MediaRecorder。
AudioRecord录音
主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫、语音)。
所以就用这个录WAV文件。这样录的文件是未经过压缩的所以,文件必要大,但是听起来也比较清晰,也支持很多其他的跨平台设备
MediaRecorder录音
已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有.aac(API = 16) .amr .3gp。
使用非常简单,MediaRecorder录音,文件小,音频是经过压缩的,所以听起来,可能效果不太好
代码实现
使用录音的代码public class MainActivity extends AppCompatActivity { private RippleDiffuse rippleDiffuse; private RecordHintDialog recordHintDialog; // 获取类的实例 ExtAudioRecorder recorder; //录音地址 String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "media.wav"; //dialog显示的图片 private Drawable[] micImages; private int touchSlop; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); touchSlop = ViewConfiguration.get(MainActivity.this).getScaledTouchSlop(); micImages = getRecordAnimPic(getResources()); recordHintDialog = new RecordHintDialog(this, R.style.DialogStyle); AuditRecorderConfiguration configuration = new AuditRecorderConfiguration.Builder() .recorderListener(listener) .handler(handler) .uncompressed(true) .builder(); recorder = new ExtAudioRecorder(configuration); initViews(); } /** 设置Dialog的图片 */ Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); recordHintDialog.setImage(micImages[msg.what]); } }; /** 录音失败的提示 */ ExtAudioRecorder.RecorderListener listener = new ExtAudioRecorder.RecorderListener() { @Override public void recordFailed(FailRecorder failRecorder) { if (failRecorder.getType() == FailRecorder.FailType.NO_PERMISSION) { Toast.makeText(MainActivity.this, "录音失败,可能是没有给权限", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "发生了未知错误", Toast.LENGTH_SHORT).show(); } } }; private void initViews() { rippleDiffuse = (RippleDiffuse) findViewById(R.id.rd); rippleDiffuse.setBtnOnTouchListener(new View.OnTouchListener() { float downY; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downY = event.getY(); //检查sdcard if (!isExitsSdcard()) { String needSd = getResources().getString(R.string.Send_voice_need_sdcard_support); Toast.makeText(MainActivity.this, needSd, Toast.LENGTH_SHORT).show(); return false; } // 设置输出文件 recorder.setOutputFile(filePath); recorder.prepare(); recorder.start(); //弹出dialog if (recorder.getState() != ExtAudioRecorder.State.ERROR) { recordHintDialog.show(); recordHintDialog.moveUpToCancel(); return true; } return false; case MotionEvent.ACTION_MOVE: { if (recorder.getState() != ExtAudioRecorder.State.RECORDING) { return false; } float offsetY = downY - event.getY(); if (offsetY > touchSlop) { recordHintDialog.releaseToCancel(); } else { recordHintDialog.moveUpToCancel(); } return true; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: recordHintDialog.dismiss(); if (recorder.getState() != ExtAudioRecorder.State.RECORDING) { return false; } float offsetY = downY - event.getY(); if (offsetY > touchSlop) { //删除录音 recorder.discardRecording(); } else { //录音成功 int time = recorder.stop(); if (time > 0) { //成功的处理 } else { String st2 = getResources().getString(R.string.The_recording_time_is_too_short); Toast.makeText(MainActivity.this, st2, Toast.LENGTH_SHORT).show(); } } recorder.reset(); return true; } return false; } }); } /** * Sdcard是否存在 */ public static boolean isExitsSdcard() { return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); } /** * 获取录音时动画效果的图片 */ public static Drawable[] getRecordAnimPic(Resources res) { return new Drawable[]{res.getDrawable(R.mipmap.record_animate_01), res.getDrawable(R.mipmap.record_animate_02), res.getDrawable(R.mipmap.record_animate_03), res.getDrawable(R.mipmap.record_animate_04), res.getDrawable(R.mipmap.record_animate_05), res.getDrawable(R.mipmap.record_animate_06), res.getDrawable(R.mipmap.record_animate_07), res.getDrawable(R.mipmap.record_animate_08), res.getDrawable(R.mipmap.record_animate_09), res.getDrawable(R.mipmap.record_animate_10), res.getDrawable(R.mipmap.record_animate_11), res.getDrawable(R.mipmap.record_animate_12), res.getDrawable(R.mipmap.record_animate_13), res.getDrawable(R.mipmap.record_animate_14)}; } }
不多不少,主要的核心代码都在onTouch里面。录音的实现使用的网上的ExtAudioRecorder.java,本来想转出是谁写,可网络上的太多了,最后也不知道是谁写的。我根据这个类,小做修改了一下,我在这个类的基础上加了一个,失败的监听,当然如果读者发现了其他的失败原因可以在FailType里面在加一些枚举类型。除了这个还加了一个任务,这个任务就是,每100毫秒获取一下Amplitude(振幅),如果设置了handler,则给handler一个消息。
在刚才的代码中看到AuditRecorderConfiguration这个类,这个类就像他的名字一样录音的些配置。
public class AuditRecorderConfiguration { public static final int[] SAMPLE_RATES = {44100, 22050, 11025, 8000}; public static final boolean RECORDING_UNCOMPRESSED = true; public static final boolean RECORDING_COMPRESSED = false; private ExtAudioRecorder.RecorderListener listener; private boolean uncompressed; private int timerInterval; private int rate; private int source; private int channelConfig; private int format; private Handler handler; /** * 创建一个默认的配置 <br /> * <ul> * <li>uncompressed = false</li> * <li>timerInterval = 120</li> * <li>rate = 8000</li> * <li>source = {@link MediaRecorder.AudioSource#MIC}</li> * <li>channelConfig = {@link AudioFormat#CHANNEL_CONFIGURATION_MONO}</li> * <li>format = {@link AudioFormat#ENCODING_PCM_16BIT}</li> * </ul> * */ public static AuditRecorderConfiguration createDefaule(){ return new Builder().builder(); } public ExtAudioRecorder.RecorderListener getRecorderListener(){ return listener; } public boolean isUncompressed(){ return uncompressed; } public int getTimerInterval(){ return timerInterval; } public int getRate(){ return rate; } public int getSource(){ return source; } public int getFormat(){ return format; } public Handler getHandler(){ return handler; } public int getChannelConfig(){ return channelConfig; } private AuditRecorderConfiguration(Builder builder){ this.listener = builder.listener; this.uncompressed = builder.uncompressed; this.timerInterval = builder.timerInterval; this.rate = builder.rate; this.source = builder.source; this.format = builder.format; this.handler = builder.handler; this.channelConfig = builder.channelConfig; } public static class Builder{ private ExtAudioRecorder.RecorderListener listener; private boolean uncompressed; private int timerInterval = 120; private int rate = SAMPLE_RATES[3]; private int source = MediaRecorder.AudioSource.MIC; private int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO; private int format = AudioFormat.ENCODING_PCM_16BIT; private Handler handler; /** 声道设置 */ public Builder getChannelConfig(int channelConfig){ this.channelConfig = channelConfig; return this; } /** 录音失败的监听 */ public Builder recorderListener(ExtAudioRecorder.RecorderListener listener){ this.listener = listener; return this; } /** 是否压缩录音 */ public Builder uncompressed(boolean uncompressed){ this.uncompressed = uncompressed; return this; } /** 周期的时间间隔 */ public Builder timerInterval(int timeInterval){ timerInterval = timeInterval; return this; } /** 采样率 */ public Builder rate(int rate){ this.rate = rate; return this; } /** 音频源 */ public Builder source(int source){ this.source = source; return this; } /** 编码制式和采样大小 */ public Builder format(int format){ this.format = format; return this; } /** 返回what是振幅值 1-13 */ public Builder handler(Handler handler){ this.handler = handler; return this; } public AuditRecorderConfiguration builder(){ return new AuditRecorderConfiguration(this); } } }
没有什么,因为录音的核心代码都在ExtAudioRecorder.java里面。这个类可以录WAV和AMR两种格式的文件,所以,基本每个方法都会通过uncompressed字段来判断,录哪种文件。
AMR
amr的录音比较简单MediaRecorder都做了相关处理,所以设置完一些配置之后,只需要调用录音方法就可以了。如何使用MediaRecorder,它的注释写的很全如:
WAV
这个相对比较复杂,需要写WAV的头文件,设置监听,然后在往文件里面写数据。
/**@param uncompressed 是否压缩录音 true不压缩,false压缩 * @param audioSource 音频源:指的是从哪里采集音频。通过 {@link AudioRecord} 的一些常量去设置 * @param sampleRate 采样率:音频的采样频率,每秒钟能够采样的次数,采样率越高,音质越高。给出的实例是44100、22050、11025但不限于这几个参数。 * 例如要采集低质量的音频就可以使用4000、8000等低采样率。 * @param channelConfig 声道设置:Android支持双声道立体声和单声道。MONO单声道,STEREO立体声 * @param audioFormat 编码制式和采样大小:采集来的数据当然使用PCM编码(脉冲代码调制编码,即PCM编码。PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。) * android支持的采样大小16bit 或者8bit。当然采样大小越大,那么信息量越多,音质也越高,现在主流的采样大小都是16bit,在低质量的语音传输的时候8bit足够了。 * @param bufferSize:录制缓冲大小:可以通过getMinBufferSize来获取 */ audioRecorder = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat, bufferSize); audioRecorder.setRecordPositionUpdateListener(updateListener); audioRecorder.setPositionNotificationPeriod(framePeriod);
看光初始化就这一大坨代码。
重点是后面的2行代码,设置了一个监听,和一个通知周期。这个通知周期就是,每次通过audioRecorder.read()方法读取framePeriod这么多的数据了就会回调updateListener的onPeriodicNotification方法,然后我们在这个方法里面,一边read,一遍往sd卡写数据。
prepare录音时的准备
// 写文件头 randomAccessWriter = new RandomAccessFile(filePath, "rw"); //设置文件长度为0,为了防止这个file以存在 randomAccessWriter.setLength(0); randomAccessWriter.writeBytes("RIFF"); //不知道文件最后的大小,所以设置0 randomAccessWriter.writeInt(0); randomAccessWriter.writeBytes("WAVE"); randomAccessWriter.writeBytes("fmt "); // Sub-chunk // size, // 16 // for // PCM randomAccessWriter.writeInt(Integer.reverseBytes(16)); // AudioFormat, 1 为 PCM randomAccessWriter.writeShort(Short.reverseBytes((short) 1)); // 数字为声道, 1 为 mono, 2 为 stereo randomAccessWriter.writeShort(Short.reverseBytes(channels)); // 采样率 randomAccessWriter.writeInt(Integer.reverseBytes(configuration.getRate())); // 采样率, SampleRate*NumberOfChannels*BitsPerSample/8 randomAccessWriter.writeInt(Integer.reverseBytes(configuration.getRate() * samples * channels / 8)); randomAccessWriter.writeShort(Short.reverseBytes((short) (channels * samples / 8))); // Block // align, // NumberOfChannels*BitsPerSample/8 randomAccessWriter.writeShort(Short.reverseBytes(samples)); // Bits per sample randomAccessWriter.writeBytes("data"); randomAccessWriter.writeInt(0); // Data chunk size not
这跟MediaRecorder直接不能比呀,MediaRecorder一个prepare方法就足够了。
关于AWV的头文件可以查看这篇文章
http://blog.csdn.net/bluesoal/article/details/932395
开始录音
audioRecorder.startRecording(); audioRecorder.read(buffer, 0, buffer.length);
启动录音后,要先read一次,才会通知激活 listener,然后再listener里面在一边read,一边写。
停止录音
audioRecorder.stop(); //头文件 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();
ExtAudioRecorder.java全部代码
public class ExtAudioRecorder {
private AuditRecorderConfiguration configuration;
public interface RecorderListener {
void recordFailed(FailRecorder failRecorder);
}
public ExtAudioRecorder(AuditRecorderConfiguration configuration) {
this.configuration = configuration;
if (configuration.isUncompressed()) {
init(configuration.isUncompressed(),
configuration.getSource(),
configuration.getRate(),
configuration.getChannelConfig(),
configuration.getFormat());
} else {
int i = 0;
do {
init(configuration.isUncompressed(),
configuration.getSource(),
AuditRecorderConfiguration.SAMPLE_RATES[i],
configuration.getChannelConfig(),
configuration.getFormat());
}
while ((++i < AuditRecorderConfiguration.SAMPLE_RATES.length) & !(getState() == ExtAudioRecorder.State.INITIALIZING));
}
}
/**
* 录音的状态
*/
public enum State {
/**
* 录音初始化
*/
INITIALIZING,
/**
* 已准备好录音
*/
READY,
/**
* 录音中
*/
RECORDING,
/**
* 录音生了错误
*/
ERROR,
/**
* 停止录音
*/
STOPPED
}
// 不压缩将使用这个进行录音
private AudioRecord audioRecorder = null;
// 压缩将使用这进行录音
private MediaRecorder mediaRecorder = null;
// 当前的振幅 (只有在未压缩的模式下)
private int cAmplitude = 0;
// 录音状态
private State state;
// 文件 (只有在未压缩的模式下)
private RandomAccessFile randomAccessWriter;
private int bufferSize;
// 录音 通知周期(只有在未压缩的模式下)
private int framePeriod;
// 输出的字节(只有在未压缩的模式下)
private byte[] buffer;
private short samples;
private short channels;
// 写入头文件的字节数(只有在未压缩的模式下)
// after stop() is called, this size is written to the header/data chunk in
// the wave file
private int payloadSize;
//录音的开始时间
private long startTime;
private String filePath;
/**
* 返回录音的状态
*
* @return 录音的状态
*/
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 (samples == 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) {
e.printStackTrace();
Log.e(ExtAudioRecorder.class.getName(), "Error occured in updateListener, recording is aborted");
//stop();
}
}
public void onMarkerReached(AudioRecord recorder) {
// NOT USED
}
};
/**
* 默认的构造方法,如果压缩录音,剩下的参数可以为0.这个方法不会抛出异常,但是会设置状态为 {@link State#ERROR}
*
* @param uncompressed 是否压缩录音 true不压缩,false压缩
* @param audioSource 音频源:指的是从哪里采集音频。通过 {@link AudioRecord} 的一些常量去设置
* @param sampleRate 采样率:音频的采样频率,每秒钟能够采样的次数,采样率越高,音质越高。给出的实例是44100、22050、11025但不限于这几个参数。
* 例如要采集低质量的音频就可以使用4000、8000等低采样率。
* @param channelConfig 声道设置:Android支持双声道立体声和单声道。MONO单声道,STEREO立体声
* @param audioFormat 编码制式和采样大小:采集来的数据当然使用PCM编码(脉冲代码调制编码,即PCM编码。PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。)
* android支持的采样大小16bit 或者8bit。当然采样大小越大,那么信息量越多,音质也越高,现在主流的采样大小都是16bit,在低质量的语音传输的时候8bit足够了。
*/
public void init(boolean uncompressed, int audioSource, int sampleRate, int channelConfig, int audioFormat) {
try {
if (uncompressed) { // RECORDING_UNCOMPRESSED
if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) {
samples = 16;
} else {
samples = 8;
}
if (channelConfig == AudioFormat.CHANNEL_CONFIGURATION_MONO) {
channels = 1;
} else {
channels = 2;
}
framePeriod = sampleRate * configuration.getTimerInterval() / 1000;
bufferSize = framePeriod * 2 * samples * channels / 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 * samples * channels / 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) {
fireFailEvent(FailRecorder.FailType.NO_PERMISSION, 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;
}
}
/**
* 设置输出的文件路径
*
* @param argPath 文件路径
*/
public void setOutputFile(String argPath) {
try {
if (state == State.INITIALIZING) {
filePath = argPath;
if (!configuration.isUncompressed()) {
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;
fireFailEvent(FailRecorder.FailType.UNKNOWN, e);
}
}
/**
* 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 (configuration.isUncompressed()) {
int result = cAmplitude;
cAmplitude = 0;
return result;
} else {
try {
return mediaRecorder.getMaxAmplitude();
} catch (IllegalStateException e) {
return 0;
}
}
} else {
return 0;
}
}
/**
* 准备录音的录音机, 如果 state 不是 {@link State#INITIALIZING} 或文件路径为null
* 将设置 state 为 {@link State#ERROR}。如果发生异常不会抛出,而是设置 state 为
* {@link State#ERROR}
*/
public void prepare() {
try {
if (state == State.INITIALIZING) {
if (configuration.isUncompressed()) {
if ((audioRecorder.getState() == AudioRecord.STATE_INITIALIZED) & (filePath != null)) {
// 写文件头 randomAccessWriter = new RandomAccessFile(filePath, "rw"); //设置文件长度为0,为了防止这个file以存在 randomAccessWriter.setLength(0); randomAccessWriter.writeBytes("RIFF"); //不知道文件最后的大小,所以设置0 randomAccessWriter.writeInt(0); randomAccessWriter.writeBytes("WAVE"); randomAccessWriter.writeBytes("fmt "); // Sub-chunk // size, // 16 // for // PCM randomAccessWriter.writeInt(Integer.reverseBytes(16)); // AudioFormat, 1 为 PCM randomAccessWriter.writeShort(Short.reverseBytes((short) 1)); // 数字为声道, 1 为 mono, 2 为 stereo randomAccessWriter.writeShort(Short.reverseBytes(channels)); // 采样率 randomAccessWriter.writeInt(Integer.reverseBytes(configuration.getRate())); // 采样率, SampleRate*NumberOfChannels*BitsPerSample/8 randomAccessWriter.writeInt(Integer.reverseBytes(configuration.getRate() * samples * channels / 8)); randomAccessWriter.writeShort(Short.reverseBytes((short) (channels * samples / 8))); // Block // align, // NumberOfChannels*BitsPerSample/8 randomAccessWriter.writeShort(Short.reverseBytes(samples)); // Bits per sample randomAccessWriter.writeBytes("data"); randomAccessWriter.writeInt(0); // Data chunk size not
// known yet, write 0
buffer = new byte[framePeriod * samples / 8 * channels];
state = State.READY;
} else {
Log.e(ExtAudioRecorder.class.getName(),
"prepare() method called on uninitialized recorder");
state = State.ERROR;
fireFailEvent(FailRecorder.FailType.UNKNOWN, null);
}
} else {
mediaRecorder.prepare();
state = State.READY;
}
} else {
Log.e(ExtAudioRecorder.class.getName(), "prepare() method called on illegal state");
release();
state = State.ERROR;
fireFailEvent(FailRecorder.FailType.UNKNOWN, null);
}
} 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;
fireFailEvent(FailRecorder.FailType.UNKNOWN, e);
}
}
/**
* 释放与这个类相关的资源,和移除不必要的文件,在必要的时候
*/
public void release() {
if (state == State.RECORDING) {
stop();
} else {
if ((state == State.READY) & (configuration.isUncompressed())) {
try {
randomAccessWriter.close(); // 删除准备文件
} catch (IOException e) {
Log.e(ExtAudioRecorder.class.getName(), "I/O exception occured while closing output file");
}
(new File(filePath)).delete();
}
}
if (configuration.isUncompressed()) {
if (audioRecorder != null) {
audioRecorder.release();
}
} else {
if (mediaRecorder != null) {
mediaRecorder.release();
}
}
}
public void discardRecording() {
stop();
File file = new File(filePath);
if (file.exists() && !file.isDirectory()) {
file.delete();
}
}
/**
* 重置录音,并设置 state 为 {@link State#INITIALIZING},如果当前状态为 {@link State#RECORDING},将会停止录音。
* 这个方法不会抛出异常,但是会设置状态为 {@link State#ERROR}
*/
public void reset() {
try {
if (state != State.ERROR) {
release();
filePath = null; // Reset file path
cAmplitude = 0; // Reset amplitude
if (configuration.isUncompressed()) {
audioRecorder = new AudioRecord(configuration.getSource(), configuration.getRate(),
channels + 1, configuration.getFormat(), bufferSize);
audioRecorder.setRecordPositionUpdateListener(updateListener);
audioRecorder.setPositionNotificationPeriod(framePeriod);
} 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;
fireFailEvent(FailRecorder.FailType.UNKNOWN, e);
}
}
/**
* 开始录音,并设置 state 为 {@link State#RECORDING}。在调用这个方法前必须调用 {@link ExtAudioRecorder#prepare()} 方法
*/
public void start() {
if (state == State.READY) {
if (configuration.isUncompressed()) {
payloadSize = 0;
audioRecorder.startRecording(); audioRecorder.read(buffer, 0, buffer.length);
} else {
mediaRecorder.start();
}
state = State.RECORDING;
this.startTime = (new Date()).getTime();
startGetMaxAmplitudeThread();
} else {
Log.e(ExtAudioRecorder.class.getName(), "start() called on illegal state");
state = State.ERROR;
fireFailEvent(FailRecorder.FailType.UNKNOWN, null);
}
}
/**
* 停止录音,并设置 state 为 {@link State#STOPPED}。如果要继续使用,则需要调用 {@link #reset()} 方法
*
* @return 录音的时间
*/
public int stop() {
if (state == State.RECORDING) {
if (configuration.isUncompressed()) {
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 {
try{
mediaRecorder.stop();
} catch (Exception e){}
}
state = State.STOPPED;
File file = new File(filePath);
if (file.exists() && file.isFile()) {
if (file.length() == 0L) {
file.delete();
return 0;
} else {
int time = (int) ((new Date()).getTime() - this.startTime) / 1000;
return time;
}
} else {
return 0;
}
} else {
Log.e(ExtAudioRecorder.class.getName(), "stop() called on illegal state");
state = State.ERROR;
fireFailEvent(FailRecorder.FailType.UNKNOWN, null);
return 0;
}
}
private void startGetMaxAmplitudeThread() {
if (configuration.getHandler() != null) {
new Thread(new Runnable() {
public void run() {
while (true) {
if (state == State.RECORDING) {
Message var1 = new Message();
var1.what = getMaxAmplitude() * 13 / 32767;
configuration.getHandler().sendMessage(var1);
SystemClock.sleep(100L);
continue;
}
return;
}
}
}).start();
}
}
/** Converts a byte[2] to a short, in LITTLE_ENDIAN format */
private short getShort(byte argB1, byte argB2) {
return (short) (argB1 | (argB2 << 8));
}
private void fireFailEvent(final FailRecorder.FailType failType, final Throwable failCause) {
if (configuration.getRecorderListener() != null) {
Runnable r = new Runnable() {
@Override
public void run() {
configuration.getRecorderListener().recordFailed(new FailRecorder(failType, failCause));
}
};
r.run();
}
}
}
下载地址
https://github.com/wu-liao-de-ren-sheng/AndroidRecord
http://download.csdn.net/detail/wu_liao_de_ren_sheng/9541715
参考资料:
http://blog.csdn.net/xu_fu/article/details/12648845
https://yq.aliyun.com/articles/8637
http://blog.csdn.net/jbgtwang/article/details/20642351
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories