美文网首页
使用 AudioRecord 录制 PCM、WAV 文件,Aud

使用 AudioRecord 录制 PCM、WAV 文件,Aud

作者: StevenChou | 来源:发表于2018-07-15 15:49 被阅读55次

    概述

    AudioRecord、AudioTrack 的使用都比较简单,基本流程:

    AudioRecord:

    1. AudioRecord.getMinBufferSize()
    2. new AudioRecord()
    3. mAudioRecord.read()

    如此,即可得到录制好的音频裸数据(PCM)。

    AudioTrack 有两种播放方式:

    1. static:把音频数据一次性写入 AudioTrack,再进行播放
    2. stream:边写边播

    播放流程基本都是:

    1. AudioTrack.getMinBufferSize()
    2. new AudioTrack()
    3. mAudioTrack.write() // static 方式必须先 write,再 play
    4. mAudioTrack.play()

    其中 getMinBufferSize()、new AudioRecord()、new AudioTrack() 需要传入 sampleRate、channel、sampleFormat 等参数,这些参数涉及到音频开发的一些基础知识,可参考我的另一篇博客:音视频开发基础概述 - PCM、YUV、H264、常用软件介绍

    AudioRecord 录制 PCM 数据

    public class AudioRecorder {
    
        private static final String TAG = "AudioRecorder";
    
        private static final int DEFAULT_SAMPLE_RATE = 48000;
        private static final int DEFAULT_PCM_DATA_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
    
        private ExecutorService mExecutor = Executors.newCachedThreadPool();
        private AudioRecord mAudioRecord;
        private int mBufferSize;
        private int mSampleRate = DEFAULT_SAMPLE_RATE;
        private int mPcmFormat = DEFAULT_PCM_DATA_FORMAT;
    
        private AudioRecordCallback mRecordCallback;
        private Handler mHandler;
        private boolean mIsRecording = false;
    
        public void setSampleRate(int sampleRate) {
            mSampleRate = sampleRate;
        }
    
        public void setPcmFormat(int pcmFormat) {
            mPcmFormat = pcmFormat;
        }
    
        public void setRecordCallback(AudioRecordCallback recordCallback) {
            mRecordCallback = recordCallback;
        }
    
        public int getChannels() {
            return 1;
        }
    
        public boolean start() {
            try {
                mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, AudioFormat.CHANNEL_IN_MONO,
                        mPcmFormat);
                mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, mSampleRate,
                        AudioFormat.CHANNEL_IN_MONO, mPcmFormat, mBufferSize);
            } catch (Exception e) {
                Log.e(TAG, "init AudioRecord exception: " + e.getLocalizedMessage());
                return false;
            }
    
            if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
                Log.e(TAG, "cannot init AudioRecord");
                return false;
            }
            mIsRecording = true;
            mExecutor.execute(this::record);
            mHandler = new Handler(Looper.myLooper());
    
            return true;
        }
    
        private void record() {
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
            if (mAudioRecord == null || mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
                return;
            }
    
            ByteBuffer audioBuffer = ByteBuffer.allocate(mBufferSize);
            mAudioRecord.startRecording();
            Log.d(TAG, "AudioRecorder started");
    
            int readResult;
            while (mIsRecording) {
                readResult = mAudioRecord.read(audioBuffer.array(), 0, mBufferSize);
                if (readResult > 0 && mRecordCallback != null) {
                    byte[] data = new byte[readResult];
                    audioBuffer.position(0);
                    audioBuffer.limit(readResult);
                    audioBuffer.get(data, 0, readResult);
                    mHandler.post(() -> mRecordCallback.onRecordSample(data));
                }
            }
    
            release();
            Log.d(TAG, "AudioRecorder finished");
        }
    
        public void stop() {
            mIsRecording = false;
        }
    
        private void release() {
            if (mAudioRecord != null) {
                mAudioRecord.stop();
                mAudioRecord.release();
                mAudioRecord = null;
            }
        }
    
        public interface AudioRecordCallback {
            // start 在哪个线程调用,就运行在哪个线程
            void onRecordSample(byte[] data);
        }
    
    }
    

    PCM 转 WAV 文件

    wav 是一种音频编码算法,它不会压缩音频,其中一种实现是在 PCM 数据前面加上 44 字节,分别描述 PCM 的采样率、声道数、数据格式等信息。

    根据 wiki 的描述,wav 文件头如下:

    WAV 文件头

    代码实现:

    private void pcmToWav(File pcmFile, File wavFile) {
            FileInputStream fis = null;
            FileOutputStream fos = null;
            try {
                fis = new FileInputStream(pcmFile);
                fos = new FileOutputStream(wavFile);
    
                int sampleFormat = mPcmDataFormat == AudioFormat.ENCODING_PCM_16BIT ? 16 : 8;
                writeWavHeader(fos, fis.getChannel().size(), sampleFormat, mSampleRate, mChannels);
    
                int channelConfig =mChannels == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO;
                int bufferSize = AudioRecord.getMinBufferSize(mSampleRate, channelConfig, mPcmDataFormat);
    
                byte[] data = new byte[bufferSize];
                while (fis.read(data) != -1) {
                    fos.write(data);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fis != null) {
                        fis.close();
                    }
    
                    if (fos != null) {
                        fos.flush();
                        fos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private void writeWavHeader(@NonNull FileOutputStream fos, long pcmDataLength, int sampleFormat,
                                    int sampleRate, int channels) throws IOException {
            long audioDataLength = pcmDataLength + 36;
            long bitRate = sampleRate * channels * sampleFormat / 8;
            byte[] header = new byte[44];
            // RIFF
            header[0] = 'R';
            header[1] = 'I';
            header[2] = 'F';
            header[3] = 'F';
            // pcm data length
            header[4] = (byte) (pcmDataLength & 0xff);
            header[5] = (byte) ((pcmDataLength >> 8) & 0xff);
            header[6] = (byte) ((pcmDataLength >> 16) & 0xff);
            header[7] = (byte) ((pcmDataLength >> 24) & 0xff);
            // WAVE
            header[8] = 'W';
            header[9] = 'A';
            header[10] = 'V';
            header[11] = 'E';
            // 'fmt '
            header[12] = 'f';
            header[13] = 'm';
            header[14] = 't';
            header[15] = ' ';
            header[16] = 16;
            header[17] = 0;
            header[18] = 0;
            header[19] = 0;
            // 1(PCM)
            header[20] = 1;
            header[21] = 0;
            // channels
            header[22] = (byte) channels;
            header[23] = 0;
            // sample rate
            header[24] = (byte) (sampleRate & 0xff);
            header[25] = (byte) ((sampleRate >> 8) & 0xff);
            header[26] = (byte) ((sampleRate >> 16) & 0xff);
            header[27] = (byte) ((sampleRate >> 24) & 0xff);
            // bit rate
            header[28] = (byte) (bitRate & 0xff);
            header[29] = (byte) ((bitRate >> 8) & 0xff);
            header[30] = (byte) ((bitRate >> 16) & 0xff);
            header[31] = (byte) ((bitRate >> 24) & 0xff);
            header[32] = 4;
            header[33] = 0;
            // 采样精度
            header[34] = (byte) sampleFormat;
            header[35] = 0;
            // data
            header[36] = 'd';
            header[37] = 'a';
            header[38] = 't';
            header[39] = 'a';
            // data length
            header[40] = (byte) (audioDataLength & 0xff);
            header[41] = (byte) ((audioDataLength >> 8) & 0xff);
            header[42] = (byte) ((audioDataLength >> 16) & 0xff);
            header[43] = (byte) ((audioDataLength >> 24) & 0xff);
            fos.write(header);
        }
    

    AudioTrack 播放 PCM 数据

    private class AudioPlayer {
    
            private AudioTrack mAudioTrack;
            private volatile boolean mIsPlaying = false;
            private int mBufferSize;
            private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
    
            private void start() {
                if (!mPcmFile.exists()) {
                    ToastHelper.show("无音频文件,请先进行录制");
                    return;
                }
                if (mIsPlaying) {
                    ToastHelper.show("当前正在播放中");
                    return;
                }
                releaseAudioTrack();
                mBufferSize = AudioTrack.getMinBufferSize(mSampleRate, AudioFormat.CHANNEL_OUT_MONO,
                        mPcmDataFormat);
                mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, mSampleRate,
                        AudioFormat.CHANNEL_OUT_MONO, mPcmDataFormat, mBufferSize, AudioTrack.MODE_STREAM);
                mIsPlaying = true;
                mExecutor.execute(this::play);
            }
    
            private void play() {
                Log.d(TAG, "play started");
                try {
                    byte[] buffer = new byte[mBufferSize];
                    int readCount;
                    DataInputStream dis = new DataInputStream(new FileInputStream(mPcmFile));
                    while (dis.available() > 0 && mIsPlaying) {
                        readCount = dis.read(buffer);
                        if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {
                            continue;
                        }
                        if (readCount > 0) {
                            mAudioTrack.play();
                            mAudioTrack.write(buffer, 0, readCount);
                        }
                    }
                    mIsPlaying = false;
                } catch (IOException e) {
                    mIsPlaying = false;
                    Log.e(TAG, "read audio data fail: " + e.getMessage());
                }
                Log.d(TAG, "play stopped");
            }
    
            private void stop() {
                mIsPlaying = false;
                try {
                    mExecutor.shutdown();
                    mExecutor.awaitTermination(500, TimeUnit.MILLISECONDS);
                    releaseAudioTrack();
                } catch (InterruptedException e) {
                    Log.e(TAG, "stop play faild");
                }
            }
    
            private void releaseAudioTrack() {
                if (mAudioTrack != null) {
                    mAudioTrack.stop();
                    mAudioTrack.release();
                    mAudioTrack = null;
                }
            }
    
        }
    

    源码已上传到 GitHub

    相关文章

      网友评论

          本文标题:使用 AudioRecord 录制 PCM、WAV 文件,Aud

          本文链接:https://www.haomeiwen.com/subject/vmpppftx.html