Android 音视频开发 - 使用AudioRecord采集音频

序言

AudioRecord 是 Android 系统提供的用于实现录音功能的 API,官方文档是这么解释的:

AndioRecord类的主要功能是让各种Java应用能够管理音频资源,以便它们通过此类能够录制声音相关的硬件所收集的声音。此功能的实现就是通过“pulling”(读取)AudioRecord对象的声音数据来完成的。在录音过程中,应用所需要做的就是通过后面三个类方法中的一个去及时地获取AudioRecord对象的录音数据。AudioRecord类提供的三个获取声音数据的方法分别是read(byte[], int, int),、read(short[], int, int)和read(ByteBuffer, int)。无论选择使用那一个方法都必须事先设定方便用户的声音数据存储格式。

开始录音的时候,AudioRecord需要初始化一个相关联的声音buffer, 这个buffer主要是用来保存新的声音数据。这个buffer的大小,我们可以在对象构造期间去指定。它表明一个AudioRecord对象还没有被读取(同步)声音数据前能录多长的音(即一次可以录制的声音容量)。声音数据从音频硬件中被读出,数据大小不超过整个录音数据的大小(可以分多次读出),即每次读取初始化buffer容量的数据。

实现Android录音的流程为:

  1. 构造一个AudioRecord对象,其中最小录音缓存buffer大小可以通过getMinBufferSize方法得到。如果buffer容量过小,将导致对象构造的失败。
  2. 初始化一个buffer,该buffer大于等于AudioRecord对象用于写声音数据的buffer大小。
  3. 开始录音
  4. 创建一个数据流,一边从AudioRecord中读取声音数据到初始化的buffer,一边将buffer中数据导入数据流。
  5. 关闭数据流
  6. 停止录音

其中,构造 AudioRecord 对象,需要这么几个参数:

  1. 音频源:一般可以使用麦克风作为采集音频的数据源。
  2. 采样率:一秒内对声音数据的采样次数,采样率越高,音质越好。
  3. 音频通道:单声道,双声道等。
  4. 音频格式:一般选用 PCM 格式,即原始的音频样本。
  5. 缓冲区大小:音频数据写入缓冲区的总数,可以通过 AudioRecord.getMinBufferSize 获取最小的缓冲区。

注意,最后生成的音频文件是 PCM 格式的,也就是最原始的音频数据,它没有头信息,不能直接播放,必须转换成可识别的格式才行。这里我们把它转成 WAV 格式,在文件的数据开头加入 WAVE HEAD 即可。

用代码实践一下录音的过程

  1. 创建录音对象,指定具体的参数。
    public void createAudio(String fileName, int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {
        // 获得缓冲区字节大小
        mBufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
        mAudioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, mBufferSizeInBytes);
        int state = mAudioRecord.getState();
        Log.i(TAG, "createAudio state:" + state + ", initialized:" + (state == AudioRecord.STATE_INITIALIZED));
        mFileName = fileName;
        mStatus = Status.STATUS_READY;
    }

  1. 开始录音,不断读取 Buffer 中声音数据,并保存到文件。
    public void startRecord() {
        if (mStatus == Status.STATUS_NO_READY || mAudioRecord == null) {
            throw new IllegalStateException("录音尚未初始化");
        }
        if (mStatus == Status.STATUS_START) {
            throw new IllegalStateException("正在录音...");
        }
        Log.d(TAG, "===startRecord===");
        mAudioRecord.startRecording();
        String currentFileName = FileUtils.getPcmFilePath(mContext, mFileName);
        final String finalFileName = currentFileName;
        //将录音状态设置成正在录音状态
        mStatus = Status.STATUS_START;

        //使用线程池管理线程
        mExecutorService.execute(new Runnable() {
            @Override
            public void run() {
                writeDataToFile(finalFileName);
            }
        });
    }

    private void writeDataToFile(String currentFileName) {
        byte[] audioData = new byte[mBufferSizeInBytes];
        BufferedOutputStream bos = null;
        int readSize;
        try {
            File file = new File(currentFileName);
            if (file.exists()) {
                file.delete();
            }
            bos = new BufferedOutputStream(new FileOutputStream(file));

            while (mStatus == Status.STATUS_START) {
                readSize = mAudioRecord.read(audioData, 0, mBufferSizeInBytes);
                if (AudioRecord.ERROR_INVALID_OPERATION != readSize) {
                    try {
                        bos.write(audioData);
                        if (mRecordStreamListener != null) {
                            mRecordStreamListener.onRecording(audioData, 0, audioData.length);
                        }
                    } catch (IOException e) {
                        Log.e(TAG, e.getMessage());
                    }
                }
            }
            bos.flush();
            if (mRecordStreamListener != null) {
                mRecordStreamListener.finishRecord();
            }
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        } finally {
            try {
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
            }
        }
    }

    public interface RecordStreamListener {
        /**
         * 录音过程中
         *
         * @param bytes
         * @param offset
         * @param length
         */
        void onRecording(byte[] bytes, int offset, int length);

        /**
         * 录音完成
         */
        void finishRecord();
    }

  1. 停止录音,释放 AudioRecord
    public void stopRecord() {
        Log.d(TAG, "===stopRecord===");
        if (mStatus == Status.STATUS_NO_READY || mStatus == Status.STATUS_READY) {
            throw new IllegalStateException("录音尚未开始");
        } else {
            mAudioRecord.stop();
            mStatus = Status.STATUS_STOP;
            release();
        }
    }

    public void release() {
        Log.d(TAG, "===release===");
        if (mAudioRecord != null) {
            mAudioRecord.release();
            mAudioRecord = null;
        }
        mStatus = Status.STATUS_NO_READY;
    }

PCM 转 WAV 的代码就不贴了,有需要的可以到 GitHub 上查看。
【附录】

《Android 音视频开发 - 使用AudioRecord采集音频》 资料图

需要资料的朋友可以加入Android架构交流QQ群聊:513088520

点击链接加入群聊【Android移动架构总群】:加入群聊

获取免费学习视频,学习大纲另外还有像高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)等Android高阶开发资料免费分享。

    原文作者:Android架构
    原文地址: https://www.jianshu.com/p/9e04d8159671
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞