Android AudioRecord 录音封装及测试

20 篇文章 1 订阅
5 篇文章 0 订阅

对 AudioRecord 封装,内部管理录音状态,并读取数据到 ByteBuffer 通知外层获取录音数据。
AudioRecord 封装代码如下:

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import com.alan.audioio.audio.common.IDataAvailableListener;
import com.alan.audioio.audio.common.IOStatus;
import com.alan.audioio.audio.exception.AudioException;
import java.nio.ByteBuffer;

/**
 * Author: AlanWang4523.
 * Date: 2019-06-14 22:59.
 * Mail: alanwang4523@gmail.com
 */
public class AudioRecorder {
    private final static String TAG = AudioRecorder.class.getSimpleName();
    private IDataAvailableListener mDataAvailableListener;
    private volatile @IOStatus int mStatus;
    private final Object mLock = new Object();
    private Thread mWorkThread;
    private AudioRecord mAudioRecord;
    private ByteBuffer mDataBuffer;
    private int mBufferSizePerFrame;

    public AudioRecorder() {
        mStatus = IOStatus.UNINITIATED;
    }

    /**
     * 设置 IDataAvailableListener
     * @param dataAvailableListener 用于处理采集到的音频数据
     */
    public void setDataAvailableListener(IDataAvailableListener dataAvailableListener) {
        this.mDataAvailableListener = dataAvailableListener;
    }

    /**
     * 初始化
     * @param sampleRateInHz 采样率
     * @param channelCount 通道数
     * @param bufferSize bufferSize
     * @throws AudioException AudioException
     */
    public void init(int sampleRateInHz, int channelCount, int bufferSize) throws AudioException {
        try {
            if (bufferSize <= 0) {
                throw new AudioException("The buffer size must be greater than 0!", null);
            }
            int channelConfig = channelCount == 2 ?
                    AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;

            int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 默认采样 short 型格式
            int minBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
            mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
                    sampleRateInHz, channelConfig, audioFormat, minBufferSize);

            mBufferSizePerFrame = bufferSize;
            mDataBuffer = ByteBuffer.allocateDirect(mBufferSizePerFrame);
            mStatus = IOStatus.INITIATED;
        } catch (Exception e) {
            throw new AudioException("Init AudioRecorder Failed!", e);
        }
    }

    /**
     * 开始录音
     */
    public void start() {
        synchronized (mLock) {
            if (mStatus == IOStatus.INITIATED) {
                mWorkThread = new Thread(null, new WorkRunnable(),
                        TAG + "-" + System.currentTimeMillis());
                mStatus = IOStatus.START;
                mWorkThread.start();
            } else if (mStatus == IOStatus.PAUSE) {
                mAudioRecord.startRecording();
                mStatus = IOStatus.RESUME;
                mLock.notify();
            } else if (mStatus == IOStatus.RESUME) {
                return;
            } else {
                throw new IllegalStateException();
            }
        }
    }

    /**
     * 暂停录音
     */
    public void pause() {
        synchronized (mLock) {
            if (mStatus == IOStatus.START || mStatus == IOStatus.RESUME) {
                mAudioRecord.stop();
                mStatus = IOStatus.PAUSE;
            }
        }
    }

    /**
     * 恢复录音
     */
    public void resume() {
        synchronized (mLock) {
            if (mStatus == IOStatus.PAUSE) {
                mAudioRecord.startRecording();
                mStatus = IOStatus.RESUME;
                mLock.notify();
            }
        }
    }

    /**
     * 停止录音
     */
    public void stop() {
        synchronized (mLock) {
            if (mStatus == IOStatus.START || mStatus == IOStatus.RESUME) {
                mStatus = IOStatus.STOP;
                // 需要调用 notify,避免在 pause 状态调用 stop 时,work thread 还在 wait
                mLock.notify();
            }
        }
    }

    /**
     * 释放资源
     */
    public void release() {
        synchronized (mLock) {
            // 如果初始化后还没开始录制则释放资源
            if (mStatus == IOStatus.INITIATED) {
                mAudioRecord.release();
            } else {
                mStatus = IOStatus.STOP;
                // 需要调用 notify,避免在 pause 状态调用 release 时,work thread 还在 wait
                mLock.notify();
            }
        }
        // 等待工作线程结束
        if (mWorkThread != null) {
            try {
                mWorkThread.join(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.mDataAvailableListener = null;
    }

    private class WorkRunnable implements Runnable {

        @Override
        public void run() {
            int readLen;
            int totalReadLen;
            int needReadLen;
            mAudioRecord.startRecording();
            while (true) {
                // 状态处理
                synchronized (mLock) {
                    while (mStatus == IOStatus.PAUSE) {
                        try {
                            mLock.wait();
                        } catch (InterruptedException e) {
                            // do nothing
                        }
                    }
                    if (mStatus == IOStatus.STOP || mStatus == IOStatus.UNINITIATED) {
                        break;
                    }
                }

                ByteBuffer dataBuffer = mDataBuffer;
                // 从 AudioRecord 中读取指定数量(mBufferSizePerFrame)的音频数据
                totalReadLen = 0;
                needReadLen = mBufferSizePerFrame;
                dataBuffer.position(totalReadLen);
                do {
                    readLen = mAudioRecord.read(dataBuffer.array(),
                            dataBuffer.arrayOffset() + totalReadLen, needReadLen);
                    if (readLen > 0) {
                        needReadLen -= readLen;
                        totalReadLen += readLen;
                    }
                } while ((needReadLen > 0) && (readLen >= 0));

                dataBuffer.limit(totalReadLen);
                dataBuffer.rewind();
                if (totalReadLen >= 0 && mDataAvailableListener != null) {
                    // 通知外层可以取录音数据
                    mDataAvailableListener.onDataAvailable(dataBuffer);
                }
            }

            try {
                mAudioRecord.stop();
            } catch (IllegalStateException e) {
                e.printStackTrace();
            }
            try {
                mAudioRecord.release();
            } catch (IllegalStateException e) {
                e.printStackTrace();
            }
        }
    }
}

IOStatus 状态定义:

import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Author: AlanWang4523.
 * Date: 2020/11/10 21:19.
 * Mail: alanwang4523@gmail.com
 */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
        IOStatus.UNINITIATED, IOStatus.INITIATED,
        IOStatus.START, IOStatus.PAUSE,
        IOStatus.RESUME, IOStatus.STOP})
public @interface IOStatus {
    int UNINITIATED = -1;
    int INITIATED   = 0;
    int START       = 1;
    int PAUSE       = 2;
    int RESUME      = 3;
    int STOP        = 4;
}

录音数据回调:

import java.nio.ByteBuffer;

/**
 * Author: AlanWang4523.
 * Date: 2020/11/10 21:40.
 * Mail: alanwang4523@gmail.com
 */
public interface IDataAvailableListener {
    /**
     * 有数据到来
     * @param byteBuffer 具体数据
     *      如果是来自采集端可以从 byteBuffer 读取数据,有效数据长度为 byteBuffer.limit()
     *      如果是来自播放端可以往 byteBuffer 写入数据,写完后调用 byteBuffer.limit(count) 设置有效数据长度
     */
    void onDataAvailable(ByteBuffer byteBuffer);
}

测试代码:

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.alan.audioio.R;
import com.alan.audioio.audio.AudioRecorder;
import com.alan.audioio.audio.WavFile;
import com.alan.audioio.audio.common.IDataAvailableListener;
import com.alan.audioio.audio.exception.AudioException;
import com.alan.audioio.utils.ALog;
import java.io.IOException;
import java.nio.ByteBuffer;
import androidx.appcompat.app.AppCompatActivity;

public class TestRecordToWavActivity extends AppCompatActivity implements View.OnClickListener {

    public static void launchMe(Context context) {
        context.startActivity(new Intent(context, TestRecordToWavActivity.class));
    }

    private String mVocalSavePath = "/sdcard/Alan/audio/record_wrapper.wav";
    private AudioRecorder mAudioRecorder;
    private WavFile mWavFile;
    private TextView mBtnCommonTest;
    private boolean mIsStartTest = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_common_test);

        mBtnCommonTest = findViewById(R.id.btnCommonTest);
        mBtnCommonTest.setOnClickListener(this);
        mBtnCommonTest.setText("StarTest");

    }

    @Override
    protected void onDestroy() {
        if (mIsStartTest) {
            mIsStartTest = false;
        }
        releaseRecorder();
        super.onDestroy();
    }

    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.btnCommonTest) {
            if (!mIsStartTest) {
                mIsStartTest = true;
                mBtnCommonTest.setText("Stop");

                ALog.e("onClick::startTest--------------------->>");
                initRecorder();
                mAudioRecorder.start();

            } else {
                mIsStartTest = false;
                mBtnCommonTest.setText("StarTest");
                mAudioRecorder.stop();

            }
        }
    }

    private void initRecorder() {
        int sampleRate = 44100;
        int channelCount = 1;
        int bufferSize = 1024;

        WavFile.HeadInfo headInfo = WavFile.HeadInfo.build()
                .setSampleRate(sampleRate)
                .setChannelCount(channelCount)
                .setBytePerSample(2);

        try {
            mWavFile = new WavFile(mVocalSavePath, headInfo);

            mAudioRecorder = new AudioRecorder();

            mAudioRecorder.init(sampleRate, channelCount, bufferSize);
            mAudioRecorder.setDataAvailableListener(new IDataAvailableListener() {
                @Override
                public void onDataAvailable(ByteBuffer byteBuffer) {
                    try {
                        mWavFile.write(byteBuffer.array(), byteBuffer.arrayOffset(), byteBuffer.limit());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        } catch (AudioException | IOException e) {
            e.printStackTrace();
            Toast.makeText(this, "init failed.", Toast.LENGTH_SHORT).show();
            finish();
        }
    }

    public void releaseRecorder() {
        if (mAudioRecorder != null) {
            mAudioRecorder.release();
        }
        if (mWavFile != null) {
            try {
                mWavFile.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

WavFile 封装见:Android Audio Wav 文件读写操作的封装

完整代码见:AudioIO

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值