简单介绍

android 录音功能按住说话 安卓语音录音_android 录音功能按住说话

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,它的注释写的很全如:

android 录音功能按住说话 安卓语音录音_ide_02

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方法就足够了。

开始录音

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

参考资料:

https://yq.aliyun.com/articles/8637