AudioRecord录制PCM音频

看一下AudioRecord安卓源码的构造函数(targetAPi29) image.png

主要看一下几个参数

  1. audioSource 声音来源,参考 MediaRecorder.AudioSource 这里的种类还比较多,暂时先使用MIC,其他的后续用到再说吧
  2. sampleRateInHz 音频采样率 注释上说44100Hz是全部设备都支持的,其他的如22050, 16000, 11025可能或有部分设备支持。 44100Hz 即每秒采样44100次
  3. channelConfig 音频通道,即声道数,CHANNEL_IN_MONO单声道,所有设备都支持, CHANNEL_IN_STEREO立体声,多声道,可能是两个声道?
public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT;
public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);
  1. audioFormat 音频数据格式,看源码种类也比较多,8位16位32位的pcm 这个类型有多达29种,网上常用的案例都是使用16位的pcm,我们这里先使用ENCODING_PCM_16BIT 16位的PCM
  2. bufferSizeInBytes 音频缓冲区大小,这个注释有点没看懂,全部尺寸的缓冲区当音频数据被录制时? 后面说这个值通过getMinBufferSize这个方法获取。 直接上代码看下吧
  private int mReadMinBufferSize;

    public enum RecorderStatus {
        NO_READY,
        START,
        STOP
    }

    private AudioRecord mAudioRecord;
    private volatile RecorderStatus mStatus;
    private static final int mAudioSource = MediaRecorder.AudioSource.MIC;
    private static final int mSampleRateInHz = 44100;
    private static final int mChannelConfig = AudioFormat.CHANNEL_IN_MONO;
    private static final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
    private final ExecutorService mExecutors = Executors.newSingleThreadExecutor();

    public RecorderStatus getStatus() {
        return mStatus;
    }

    public void startRecordOnMainThread(String filePath) {
        mExecutors.execute(() -> {
            try {
                startRecord(filePath);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
    }


    @SuppressLint("MissingPermission")
    @WorkerThread
    public void startRecord(String filePath) throws IOException {
        if (mStatus == RecorderStatus.START) {
            Log.i(TAG, "startRecord: already in recording");
            return;
        }
        File file = new File(filePath);
        if (file.exists()) {
            boolean delete = file.delete();
            Log.i(TAG, "startRecord: delete already exist audio file result = " + delete);
        }
        String dir = filePath.substring(0, filePath.lastIndexOf(File.separator) + 1);
        if (!new File(dir).exists()) {
            boolean mkdirs = new File(dir).mkdirs();
            Log.i(TAG, "startRecord: mkdir directory result = " + mkdirs + "   and path = " + dir);
        }
        boolean newFile = file.createNewFile();
        Log.i(TAG, "startRecord: create new Audio file result = " + newFile + "   and path = " + filePath);
        mReadMinBufferSize = AudioRecord.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);
        if (mAudioRecord == null) {
            mAudioRecord = new AudioRecord(mAudioSource, mSampleRateInHz, mChannelConfig, mAudioFormat, mReadMinBufferSize);
        }
        mStatus = RecorderStatus.START;
        mAudioRecord.startRecording();
        try (FileOutputStream fos = new FileOutputStream(file)) {
            byte[] buf = new byte[mReadMinBufferSize];
            while (mStatus == RecorderStatus.START) {
                int bufferReadResult = mAudioRecord.read(buf, 0, mReadMinBufferSize);
                if (bufferReadResult != AudioRecord.ERROR_INVALID_OPERATION) {
                    fos.write(buf);
                }
            }
        }
    }

    public void stopRecord() {
        mStatus = RecorderStatus.STOP;
        if (mAudioRecord != null) {
            mAudioRecord.stop();
        }
    }

    public void release() {
        mStatus = RecorderStatus.NO_READY;
        if (mAudioRecord != null) {
            mAudioRecord.release();
            mAudioRecord = null;
        }
    }

使用FFmpeg播放PCM音频

ffplay -ar 44100 -ac 1 -f s16le -i 2023_11_09_11_06_01.pcm
-i 表示指定的输入文件
-f 表示强制使用的格式
-ar 表示播放的音频数据的采样率
-ac 表示播放的音频数据的通道数