Android-音视频(2):用AudioRecord采集麦克风PCM并保存到文件

1.先了解一下录音流程


  • 1.定义AudioRecord录音相关参数,如 音频采集源、音频采样率、声道、数据格式、最小录音缓存
//音频采集来源
    private static final int mAudioSource = MediaRecorder.AudioSource.MIC; //麦克风
    //音频采样率 (MediaRecoder的采样率通常是8000Hz AAC的通常是44100Hz.设置采样率为44100目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)
    private static final int mSampleRateInHz = 44100;
    //声道
    private static final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO; //单声道
    //数据格式  (指定采样的数据的格式和每次采样的大小)
    //指定音频量化位数 ,在AudioFormaat类中指定了以下各种可能的常量。通常我们选择ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脉冲编码调制,它实际上是原始音频样本。
    //因此可以设置每个样本的分辨率为16位或者8位,16位将占用更多的空间和处理能力,表示的音频也更加接近真实。
    private static final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
    //指定最小录音缓冲区大小
    private int mBufferSizeInBytes;
  • 2.通过音频采样率、通道、数据格式,计算出最小录音缓冲区的大小
//计算最小录音缓冲区大小
        mBufferSizeInBytes = AudioRecord.getMinBufferSize(mSampleRateInHz,mChannelConfig,mAudioFormat);
  • 3.创建AudioRecord,参数为上面的5个
mAudioRecord = new AudioRecord(mAudioSource,mSampleRateInHz,mChannelConfig,mAudioFormat,mBufferSizeInBytes);
  • 4.创建一个文件用于保存音频PCM。
 //存储AudioRecord录音PCM数据的文件
    private File mRecordingFile;
//录音pcm数据文件路径+文件名
    private static final String mFileName = Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"audiorecordtest.pcm";
    //将PCM数据写入文件的流
    private DataOutputStream mDataOutputStream;

 //创建录音PCM数据存储的文件
        mRecordingFile = new File(mFileName);
        if (mRecordingFile.exists()){
            mRecordingFile.delete();
        }
        mRecordingFile.createNewFile();
        //初始化流
        mDataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mRecordingFile)));
  • 5.开始录音,先创建一个byte[] 大小为最小录音缓冲区大小,用于写入声音数据,然后录音,同时写入文件。
 byte[] buffer = new byte[mBufferSizeInBytes];
                //开始录音
                mAudioRecord.startRecording();

int bufferReadResult = mAudioRecord.read(buffer,0,mBufferSizeInBytes);

//如果音频数据没有错误,就写入文件
if (AudioRecord.ERROR_INVALID_OPERATION != bufferReadResult && mDataOutputStream != null){
                        for (int i=0 ; i < bufferReadResult ; i++){
                            mDataOutputStream.write(buffer[i]);
                        }
                    }
  • 6.录音完毕,关闭录音及释放相关资源.
mDataOutputStream.close();

mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
  • 7.这时候音频PCM文件已经产生,但是点击发现不能播放。所以我们需要添加文件头WAV(或其它),使其可以播放。
  • 先定义新文件 test.wav的存储路径。
//定义wav文件的位置
String wavFilename = Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"test.wav";
        File wavFile = new File(wavFilename);
        if (wavFile.exists()){
            wavFile.delete();
        }
        wavFile.createNewFile();
  • 然后开始将 PCM文件转换成WAV文件
public void pcmTowav(String pcmfilepath , String wavfilepath ) throws IOException {
        FileInputStream pcmIn;
        FileOutputStream wavOut;
        //原始pcm数据大小不含(文件头),添加文件头要用
        long pcmLength;
        //文件总大小(含文件头),添加文件头要用
        long dataLength;
        //通道标识(1(单通道)或2(双通道),添加文件头要用)
        int channels = (mChannelConfig == AudioFormat.CHANNEL_OUT_MONO ? 1 : 2);
        //采样率,添加文件头要用
        int sampleRate = mSampleRateInHz;
        //信息传输速率=((采样率*通道数*每样值位数) / 8),添加文件头要用
        int byteRate = sampleRate*channels*16/8;

        byte[] data = new byte[mBufferSizeInBytes];
        pcmIn = new FileInputStream(pcmfilepath);
        wavOut = new FileOutputStream(wavfilepath);
        pcmLength = pcmIn.getChannel().size();
        //wav文件头44字节
        dataLength = pcmLength+44;
        //先写入wav文件头
        writeHeader(wavOut , pcmLength , dataLength , sampleRate , channels , byteRate);
        //再写入数据
        while (pcmIn.read(data)!=-1){
            wavOut.write(data);
        }
        Log.i("TAG","wav文件写入完成");
        pcmIn.close();
        wavOut.close();
    }
  • 写入wav文件头的方法如下:
private void writeHeader(FileOutputStream wavOut, long pcmLength, long dataLength, int sampleRate, int channels, int byteRate) throws IOException {
        //wave文件头44个字节
        byte[] header = new byte[44];
        /*0-11字节(RIFF chunk :riff文件描述块)*/
//文件标记为RIFF文件
        header[0]='R';
        header[1]='I';
        header[2]='F';
        header[3]='F';
//文件总长度
        header[4]= (byte) (dataLength * 0xff); //取一个字节(低8位)
        header[5]= (byte) ((dataLength >> 8) * 0xff); //取一个字节 (中8位)
        header[6]= (byte) ((dataLength >> 16) * 0xff); //取一个字节 (次8位)
        header[7]= (byte) ((dataLength >> 24) * 0xff); //取一个字节 (高8位)
//文件类型标记为WAVE
        header[8]='W';
        header[9]='A';
        header[10]='V';
        header[11]='E';
        /*13-35字节(fmt chunk : 数据格式信息块)*/
//标记格式块,描述数据格式信息 4个字节
        header[12]='f';
        header[13]='m';
        header[14]='t';
        header[15]=' '; //要有一个空格
//格式数据的长度 4个字节
        header[16]=16;
        header[17]=0;
        header[18]=0;
        header[19]=0;
//格式类型(如1 是 PCM) 2个字节
        header[20]=1;
        header[21]=0;
//通道数 1为单声道,2为双声道
        header[22]= (byte) channels;
        header[23]=0;
//采样率
        header[24]= (byte) (sampleRate * 0xff);
        header[25]= (byte) ((sampleRate >> 8) * 0xff);
        header[26]= (byte) ((sampleRate >> 16) * 0xff);
        header[27]= (byte) ((sampleRate >> 24) * 0xff);
//比特率=(采样率* 每样值位数*渠道) / 8。
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
// (每样值位数* 通道)/8  所有通道的一个样值所需比特数  其中的声道数至少为2,小于2的按2算
        header[32]= (16 * 2 / 8); //(比如PCM16,双声道的1个Frame等于16*2/8=4字节),
        header[33]= 0 ;
//每样值位数(常用16比特或8比特表示样值)
        header[34]=16;
        header[35]=0;
        /*36字节之后 (data chunk : 数据块)*/
//"data"块标记,标记数据节开始。
        header[36]='d';
        header[37]='a';
        header[38]='t';
        header[39]='a';
//描述数据节的大小
        header[40] = (byte) (pcmLength & 0xff);
        header[41] = (byte) ((pcmLength >> 8) & 0xff);
        header[42] = (byte) ((pcmLength >> 16) & 0xff);
        header[43] = (byte) ((pcmLength >> 24) & 0xff);
        //写入文件头
        wavOut.write(header,0,44);
    }
  • 8.这个时候音频wav文件已经产生,可以点击播放了。

2.源码如下:


注意:录音要在线程

Activity.java

start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                    mThread = new Thread(){
                        @Override
                        public void run() {
                            try {
                                startRecord();
                            } catch (IOException e) {
                                e.printStackTrace();
                                stopRecord();
                            }
                        }
                    };
                mThread.start();
            }
        });
        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                stopRecord();
            }
        });
        addHead.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    addHead();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

private void initAudioRecord() throws IOException {
        //计算最小录音缓冲区大小
        mBufferSizeInBytes = AudioRecord.getMinBufferSize(mSampleRateInHz,mChannelConfig,mAudioFormat);
        //创建AudioRecord。AudioRecord类实际上不会保存捕获的音频,因此需要手动创建文件并保存下载。
        mAudioRecord = new AudioRecord(mAudioSource,mSampleRateInHz,mChannelConfig,mAudioFormat,mBufferSizeInBytes);
        //创建录音数据存储的文件
        mRecordingFile = new File(mFileName);
        if (mRecordingFile.exists()){
            mRecordingFile.delete();
        }
        mRecordingFile.createNewFile();
        //初始化流
        mDataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mRecordingFile)));
    }


//开始录音
    private void startRecord() throws IOException {
        //初始化AudioRecord和存储的文件和流
        initAudioRecord();
        //检测AudioRecord.getMinBufferSize的参数(采样率,声道,采样精度)是否支持当前的硬件设备
        if (mBufferSizeInBytes == AudioRecord.ERROR_BAD_VALUE || mBufferSizeInBytes == AudioRecord.ERROR){
            Log.i("TAG","Unable To getMinBufferSize");
        }else {
            //检测AudioRecord初始化成功
            if (mAudioRecord != null && mAudioRecord.getState() == AudioRecord.STATE_INITIALIZED){
                byte[] buffer = new byte[mBufferSizeInBytes];
                //开始录音
                mAudioRecord.startRecording();
                Log.i("TAG","开始录音");
                isRecording = true;
                //如果正在录音状态
                while (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING && isRecording){
                    Log.i("TAG","正在录音");
                    int bufferReadResult = mAudioRecord.read(buffer,0,mBufferSizeInBytes);
                    //如果音频数据没有错误,就写入文件
                    if (AudioRecord.ERROR_INVALID_OPERATION != bufferReadResult && mDataOutputStream != null){
                        for (int i=0 ; i < bufferReadResult ; i++){
                            mDataOutputStream.write(buffer[i]);
                        }
                    }
                }
                mDataOutputStream.close();
            }
        }
    }

//添加wav头
private void addHead() throws IOException {
        //定义wav文件的位置
        String wavFilename = Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"test.wav";
        File wavFile = new File(wavFilename);
        if (wavFile.exists()){
            wavFile.delete();
        }
        wavFile.createNewFile();
        mBufferSizeInBytes = AudioRecord.getMinBufferSize(mSampleRateInHz,mChannelConfig,mAudioFormat);
        PcmToWavUtil pcmtowavutil = new PcmToWavUtil(mSampleRateInHz,mChannelConfig,mBufferSizeInBytes,mAudioFormat);
        //添加wav文件头,并将pcm数据转换成wav文件
        pcmtowavutil.pcmTowav(mFileName,wavFilename);
    }


//停止录音,释放资源
    private void stopRecord() {
        isRecording = false;
        if (mAudioRecord != null && mAudioRecord.getState() == AudioRecord.STATE_INITIALIZED){
            mAudioRecord.stop();
            Log.i("TAG","录音停止");
        }
        if (mAudioRecord != null){
            mAudioRecord.release();
            mAudioRecord = null;
            mThread = null;
        }
    }
PcmToWavUtil.java
public class PcmToWavUtil {
    //采样率
    private int mSampleRateInHz;
    //声道数
    private int mChannelConfig;
    //最小缓冲区大小
    private int mBufferSizeInBytes;
    //数据格式
    //这里传入了AudioFormat.ENCODING_PCM_16BIT,[所以下面代码中的每样值位数为16,每样值字节为2后面也会用]
    private int mAudioFormat;

    public PcmToWavUtil(int mSampleRateInHz , int mChannelConfig , int mBufferSizeInBytes,int mAudioFormat){
        this.mSampleRateInHz = mSampleRateInHz;
        this.mChannelConfig = mChannelConfig;
        this.mBufferSizeInBytes = mBufferSizeInBytes;
        this.mAudioFormat = mAudioFormat;
    }

    public void pcmTowav(String pcmfilepath , String wavfilepath ) throws IOException {
        FileInputStream pcmIn;
        FileOutputStream wavOut;
        //原始pcm数据大小不含(文件头),添加文件头要用
        long pcmLength;
        //文件总大小(含文件头),添加文件头要用
        long dataLength;
        //通道标识(1(单通道)或2(双通道),添加文件头要用)
        int channels = (mChannelConfig == AudioFormat.CHANNEL_OUT_MONO ? 1 : 2);
        //采样率,添加文件头要用
        int sampleRate = mSampleRateInHz;
        //信息传输速率=((采样率*通道数*每样值位数) / 8),添加文件头要用
        int byteRate = sampleRate*channels*16/8;

        byte[] data = new byte[mBufferSizeInBytes];
        pcmIn = new FileInputStream(pcmfilepath);
        wavOut = new FileOutputStream(wavfilepath);
        pcmLength = pcmIn.getChannel().size();
        //wav文件头44字节
        dataLength = pcmLength+44;
        //先写入wav文件头
        writeHeader(wavOut , pcmLength , dataLength , sampleRate , channels , byteRate);
        //再写入数据
        while (pcmIn.read(data)!=-1){
            wavOut.write(data);
        }
        Log.i("TAG","wav文件写入完成");
        pcmIn.close();
        wavOut.close();
    }

    private void writeHeader(FileOutputStream wavOut, long pcmLength, long dataLength, int sampleRate, int channels, int byteRate) throws IOException {
        //wave文件头44个字节
        byte[] header = new byte[44];
        /*0-11字节(RIFF chunk :riff文件描述块)*/
        header[0]='R';
        header[1]='I';
        header[2]='F';
        header[3]='F';
        header[4]= (byte) (dataLength * 0xff); //取一个字节(低8位)
        header[5]= (byte) ((dataLength >> 8) * 0xff); //取一个字节 (中8位)
        header[6]= (byte) ((dataLength >> 16) * 0xff); //取一个字节 (次8位)
        header[7]= (byte) ((dataLength >> 24) * 0xff); //取一个字节 (高8位)
        header[8]='W';
        header[9]='A';
        header[10]='V';
        header[11]='E';
        /*13-35字节(fmt chunk : 数据格式信息块)*/
        header[12]='f';
        header[13]='m';
        header[14]='t';
        header[15]=' '; //要有一个空格
        header[16]=16;
        header[17]=0;
        header[18]=0;
        header[19]=0;
        header[20]=1;
        header[21]=0;
        header[22]= (byte) channels;
        header[23]=0;
        header[24]= (byte) (sampleRate * 0xff);
        header[25]= (byte) ((sampleRate >> 8) * 0xff);
        header[26]= (byte) ((sampleRate >> 16) * 0xff);
        header[27]= (byte) ((sampleRate >> 24) * 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        header[32]= (16 * 2 / 8); //
        header[33]= 0 ;
        header[34]=16;
        header[35]=0;
        /*36字节之后 (data chunk : 数据块)*/
        header[36]='d';
        header[37]='a';
        header[38]='t';
        header[39]='a';
        header[40] = (byte) (pcmLength & 0xff);
        header[41] = (byte) ((pcmLength >> 8) & 0xff);
        header[42] = (byte) ((pcmLength >> 16) & 0xff);
        header[43] = (byte) ((pcmLength >> 24) & 0xff);
        //写入文件头
        wavOut.write(header,0,44);
    }

}

猜你喜欢

转载自blog.csdn.net/qq_38261174/article/details/82862951