Android audio and video (2): collect microphone PCM with AudioRecord and save it to file

Keywords: encoding Java less

1. First understand the recording process

  • 1. Define AudioRecord recording related parameters, such as audio acquisition source, audio sampling rate, channel, data format, and minimum recording cache
//Audio acquisition source
    private static final int mAudioSource = MediaRecorder.AudioSource.MIC; //Microphone
    //Audio sampling rate (the sampling rate of mediadecoder is generally 8000Hz AAC and 44100Hz. Setting the sampling rate to 44100 is currently the common sampling rate. The official document indicates that this value can be compatible with all settings)
    private static final int mSampleRateInHz = 44100;
    //Vocal tract
    private static final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO; //Mono channel
    //Data format (specify the format of the sampled data and the size of each sample)
    //Specifies the number of audio quantization bits, and the following various possible constants are specified in the AudioFormaat class. Generally, we choose encoding PCM and encoding PCM to represent pulse code modulation, which is actually the original audio sample.
    //Therefore, the resolution of each sample can be set to 16 bits or 8 bits, 16 bits will take up more space and processing power, and the audio represented will be closer to the real.
    private static final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
    //Specify the minimum recording buffer size
    private int mBufferSizeInBytes;
  • 2. Calculate the minimum recording buffer size through audio sampling rate, channel and data format
//Calculate the minimum recording buffer size
        mBufferSizeInBytes = AudioRecord.getMinBufferSize(mSampleRateInHz,mChannelConfig,mAudioFormat);
  • 3. Create AudioRecord with the above five parameters
mAudioRecord = new AudioRecord(mAudioSource,mSampleRateInHz,mChannelConfig,mAudioFormat,mBufferSizeInBytes);
  • 4. Create a file to save the audio PCM.
 //File to store AudioRecord PCM data
    private File mRecordingFile;
//Recording pcm data file path + file name
    private static final String mFileName = Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"audiorecordtest.pcm";
    //Stream to write PCM data to file
    private DataOutputStream mDataOutputStream;

 //Creating a file for recording PCM data storage
        mRecordingFile = new File(mFileName);
        if (mRecordingFile.exists()){
            mRecordingFile.delete();
        }
        mRecordingFile.createNewFile();
        //Initialization flow
        mDataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mRecordingFile)));
  • 5. To start recording, first create a byte [] with the minimum recording buffer size, which is used to write sound data, then record and write files at the same time.
 byte[] buffer = new byte[mBufferSizeInBytes];
                //Start recording
                mAudioRecord.startRecording();

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

//If there is no error in the audio data, write to the file
if (AudioRecord.ERROR_INVALID_OPERATION != bufferReadResult && mDataOutputStream != null){
                        for (int i=0 ; i < bufferReadResult ; i++){
                            mDataOutputStream.write(buffer[i]);
                        }
                    }
  • 6. Close recording and release related resources after recording
mDataOutputStream.close();

mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
  • 7. At this time, the audio PCM file has been generated, but it can't be played after clicking. So we need to add the file header WAV (or other) so that it can play.
  • First define the storage path of the new file test.wav.
//Define the location of wav files
String wavFilename = Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"test.wav";
        File wavFile = new File(wavFilename);
        if (wavFile.exists()){
            wavFile.delete();
        }
        wavFile.createNewFile();
  • Then start converting PCM files to WAV files
public void pcmTowav(String pcmfilepath , String wavfilepath ) throws IOException {
        FileInputStream pcmIn;
        FileOutputStream wavOut;
        //The original pcm data size does not contain (file header). To add a file header, use
        long pcmLength;
        //The total size of the file (including the file header). To add a file header
        long dataLength;
        //Channel ID (1 (single channel) or 2 (dual channel), for adding file header)
        int channels = (mChannelConfig == AudioFormat.CHANNEL_OUT_MONO ? 1 : 2);
        //Sample rate, to add a file header
        int sampleRate = mSampleRateInHz;
        //Information transfer rate = ((sampling rate * number of channels * number of bits of each value) / 8), to add a file header
        int byteRate = sampleRate*channels*16/8;

        byte[] data = new byte[mBufferSizeInBytes];
        pcmIn = new FileInputStream(pcmfilepath);
        wavOut = new FileOutputStream(wavfilepath);
        pcmLength = pcmIn.getChannel().size();
        //wav file header 44 bytes
        dataLength = pcmLength+44;
        //Write wav file header first
        writeHeader(wavOut , pcmLength , dataLength , sampleRate , channels , byteRate);
        //Write data again
        while (pcmIn.read(data)!=-1){
            wavOut.write(data);
        }
        Log.i("TAG","wav File write complete");
        pcmIn.close();
        wavOut.close();
    }
  • Write wav file header as follows:
private void writeHeader(FileOutputStream wavOut, long pcmLength, long dataLength, int sampleRate, int channels, int byteRate) throws IOException {
        //44 bytes of wave file header
        byte[] header = new byte[44];
        /*0-11 Bytes (RIFF chunk: riff file description block)*/
//Files marked as RIFF files
        header[0]='R';
        header[1]='I';
        header[2]='F';
        header[3]='F';
//Total file length
        header[4]= (byte) (dataLength * 0xff); //Take a byte (lower 8 bits)
        header[5]= (byte) ((dataLength >> 8) * 0xff); //Take a byte (middle 8 bits)
        header[6]= (byte) ((dataLength >> 16) * 0xff); //Take a byte (8-bit times)
        header[7]= (byte) ((dataLength >> 24) * 0xff); //Take a byte (high 8 bits)
//File type is marked WAVE
        header[8]='W';
        header[9]='A';
        header[10]='V';
        header[11]='E';
        /*13-35 Byte (fmt chunk: data format information block)*/
//Mark format block, description data format information 4 bytes
        header[12]='f';
        header[13]='m';
        header[14]='t';
        header[15]=' '; //There should be a space
//Format data length 4 bytes
        header[16]=16;
        header[17]=0;
        header[18]=0;
        header[19]=0;
//Format type (e.g. 1 is PCM) 2 bytes
        header[20]=1;
        header[21]=0;
//Channel number 1 is mono, 2 is dual
        header[22]= (byte) channels;
        header[23]=0;
//sampling 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 = (sampling rate * bits of each value * channel) / 8.
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
// (bits of each sample value * channel) / 8 the number of bits required for a sample value of all channels, of which the number of channels shall be at least 2, and those less than 2 shall be calculated as 2
        header[32]= (16 * 2 / 8); //(for example, PCM16, one Frame of dual channel is equal to 16 * 2 / 8 = 4 bytes),
        header[33]= 0 ;
//Bits of each value (usually 16 bits or 8 bits for sample value)
        header[34]=16;
        header[35]=0;
        /*36 After bytes (data chunk)*/
//The "data" block marks the beginning of the data section.
        header[36]='d';
        header[37]='a';
        header[38]='t';
        header[39]='a';
//Describes the size of the data section
        header[40] = (byte) (pcmLength & 0xff);
        header[41] = (byte) ((pcmLength >> 8) & 0xff);
        header[42] = (byte) ((pcmLength >> 16) & 0xff);
        header[43] = (byte) ((pcmLength >> 24) & 0xff);
        //Write header
        wavOut.write(header,0,44);
    }
  • 8. At this time, the audio wav file has been generated and can be clicked to play.

 

2. The source code is as follows:

Note: the recording should be in the thread

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 {
        //Calculate the minimum recording buffer size
        mBufferSizeInBytes = AudioRecord.getMinBufferSize(mSampleRateInHz,mChannelConfig,mAudioFormat);
        //Create AudioRecord. The AudioRecord class does not actually save the captured audio, so you need to manually create the file and save the download.
        mAudioRecord = new AudioRecord(mAudioSource,mSampleRateInHz,mChannelConfig,mAudioFormat,mBufferSizeInBytes);
        //Create a file for recording data storage
        mRecordingFile = new File(mFileName);
        if (mRecordingFile.exists()){
            mRecordingFile.delete();
        }
        mRecordingFile.createNewFile();
        //Initialization flow
        mDataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mRecordingFile)));
    }


//Start recording
    private void startRecord() throws IOException {
        //Initializing AudioRecord and stored files and streams
        initAudioRecord();
        //Check whether the parameters (sampling rate, channel, sampling precision) of AudioRecord.getMinBufferSize support the current hardware device
        if (mBufferSizeInBytes == AudioRecord.ERROR_BAD_VALUE || mBufferSizeInBytes == AudioRecord.ERROR){
            Log.i("TAG","Unable To getMinBufferSize");
        }else {
            //Test AudioRecord initialization succeeded
            if (mAudioRecord != null && mAudioRecord.getState() == AudioRecord.STATE_INITIALIZED){
                byte[] buffer = new byte[mBufferSizeInBytes];
                //Start recording
                mAudioRecord.startRecording();
                Log.i("TAG","Start recording");
                isRecording = true;
                //If recording status
                while (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING && isRecording){
                    Log.i("TAG","Recording");
                    int bufferReadResult = mAudioRecord.read(buffer,0,mBufferSizeInBytes);
                    //If there is no error in the audio data, write to the file
                    if (AudioRecord.ERROR_INVALID_OPERATION != bufferReadResult && mDataOutputStream != null){
                        for (int i=0 ; i < bufferReadResult ; i++){
                            mDataOutputStream.write(buffer[i]);
                        }
                    }
                }
                mDataOutputStream.close();
            }
        }
    }

//Add wav header
private void addHead() throws IOException {
        //Define the location of wav files
        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);
        //Add wav file header and convert pcm data to WAV file
        pcmtowavutil.pcmTowav(mFileName,wavFilename);
    }


//Stop recording, release resources
    private void stopRecord() {
        isRecording = false;
        if (mAudioRecord != null && mAudioRecord.getState() == AudioRecord.STATE_INITIALIZED){
            mAudioRecord.stop();
            Log.i("TAG","Recording stop");
        }
        if (mAudioRecord != null){
            mAudioRecord.release();
            mAudioRecord = null;
            mThread = null;
        }
    }
PcmToWavUtil.java
public class PcmToWavUtil {
    //sampling rate
    private int mSampleRateInHz;
    //Channels
    private int mChannelConfig;
    //Minimum buffer size
    private int mBufferSizeInBytes;
    //data format
    //Here, audioformat.encoding? PCM? 16bit is passed in, [so the number of bits of each value in the following code is 16, and the byte of each value is 2, which will also be used]
    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;
        //The original pcm data size does not contain (file header). To add a file header, use
        long pcmLength;
        //The total size of the file (including the file header). To add a file header
        long dataLength;
        //Channel ID (1 (single channel) or 2 (dual channel), for adding file header)
        int channels = (mChannelConfig == AudioFormat.CHANNEL_OUT_MONO ? 1 : 2);
        //Sample rate, to add a file header
        int sampleRate = mSampleRateInHz;
        //Information transfer rate = ((sampling rate * number of channels * number of bits of each value) / 8), to add a file header
        int byteRate = sampleRate*channels*16/8;

        byte[] data = new byte[mBufferSizeInBytes];
        pcmIn = new FileInputStream(pcmfilepath);
        wavOut = new FileOutputStream(wavfilepath);
        pcmLength = pcmIn.getChannel().size();
        //wav file header 44 bytes
        dataLength = pcmLength+44;
        //Write wav file header first
        writeHeader(wavOut , pcmLength , dataLength , sampleRate , channels , byteRate);
        //Write data again
        while (pcmIn.read(data)!=-1){
            wavOut.write(data);
        }
        Log.i("TAG","wav File write complete");
        pcmIn.close();
        wavOut.close();
    }

    private void writeHeader(FileOutputStream wavOut, long pcmLength, long dataLength, int sampleRate, int channels, int byteRate) throws IOException {
        //44 bytes of wave file header
        byte[] header = new byte[44];
        /*0-11 Bytes (RIFF chunk: riff file description block)*/
        header[0]='R';
        header[1]='I';
        header[2]='F';
        header[3]='F';
        header[4]= (byte) (dataLength * 0xff); //Take a byte (lower 8 bits)
        header[5]= (byte) ((dataLength >> 8) * 0xff); //Take a byte (middle 8 bits)
        header[6]= (byte) ((dataLength >> 16) * 0xff); //Take a byte (8-bit times)
        header[7]= (byte) ((dataLength >> 24) * 0xff); //Take a byte (high 8 bits)
        header[8]='W';
        header[9]='A';
        header[10]='V';
        header[11]='E';
        /*13-35 Byte (fmt chunk: data format information block)*/
        header[12]='f';
        header[13]='m';
        header[14]='t';
        header[15]=' '; //There should be a space
        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 After bytes (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);
        //Write header
        wavOut.write(header,0,44);
    }

}

 

 

 

Posted by mrjap1 on Tue, 24 Dec 2019 07:33:01 -0800