使用AudioRecorder录音

。_饼干妹妹 提交于 2020-01-11 04:50:50

安仔夏天勤奋 - 音视频理解的知识笔记

android平台上的音频采集一般就两种方式:

1、使用MediaRecorder进行音频采集。

MediaRecorder 是基于 AudioRecorder 的 API(最终还是会创建AudioRecord用来与AudioFlinger进行交互) ,它可以直接将采集到的音频数据转化为执行的编码格式,并保存。这种方案相较于调用系统内置的用用程序,便于开发者在UI界面上布局,而且系统封装的很好,便于使用,唯一的缺点是使用它录下来的音频是经过编码的,没有办法的得到原始的音频。同时MediaRecorder即可用于音频的捕获也可以用于视频的捕获相当的强大。实际开发中没有特殊需求的话,用的是比较多的!

2、使用AudioRecord进行音频采集

AudioRecord 是一个比较偏底层的API,它可以获取到一帧帧PCM数据,之后可以对这些数据进行处理。AudioRecord这种方式采集最为灵活,使开发者最大限度的处理采集的音频,同时它捕获到的音频是原始音频PCM格式的!像做变声处理的需要就必须要用它收集音频。

3、实例化AudioRecord

  audioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,

参数介绍

//    audioResource:音频采集的来源
//    audioSampleRate:音频采样率
//    channelConfig:声道
//    audioFormat:音频采样精度,指定采样的数据的格式和每次采样的大小。
//    bufferSizeInBytes:AudioRecord 采集到的音频数据所存放的缓冲区大小。获取最小的缓冲区大小,用于存放

AudioRecorderManager .class

import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Environment;
import android.support.annotation.RequiresApi;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.Executors;

public class AudioRecorderManager {


//    audioResource:音频采集的来源
//    audioSampleRate:音频采样率
//    channelConfig:声道
//    audioFormat:音频采样精度,指定采样的数据的格式和每次采样的大小。
//    bufferSizeInBytes:AudioRecord 采集到的音频数据所存放的缓冲区大小。获取最小的缓冲区大小,用于存放AudioRecord采集到的音频数据。


    private boolean isRecording ;
    //指的是麦克风
    private int audioSource = MediaRecorder.AudioSource.MIC;
    //指定采样率 (MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100,目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)
    private int sampleRateInHz = 441000;
    //指定捕获音频的声道数目。在AudioFormat类中指定用于此的常量  (单声道CHANNEL_IN_MONO就是一个喇叭 ,双声道CHANNEL_IN_STEREO两个喇叭 )
    private int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
    //指定音频量化位数 ,在AudioFormaat类中指定了以下各种可能的常量。通常我们选择ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脉冲编码调制,它实际上是原始音频样本。
    //因此可以设置每个样本的分辨率为16位或者8位,16位将占用更多的空间和处理能力,表示的音频也更加接近真实。
    private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    //指定缓冲区大小。调用AudioRecord类的getMinBufferSize方法可以获得。
    //特别注意:这个大小不能随便设置,AudioRecord 提供对应的 API 来获取这个值。
    private int bufferSizeInBytes =  AudioRecord.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat);
    /**/
    private AudioRecord audioRecord  ;

    private static AudioRecorderManager manager ;

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public static AudioRecorderManager newInstance(){
        if (null == manager){
            synchronized (AudioRecorderManager.class){
                if (null == manager){
                    manager = new AudioRecorderManager() ;
                }
            }
        }
        return manager ;
    }



    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private AudioRecorderManager(){
        /*实例化音频捕获的实例*/
        audioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,bufferSizeInBytes);
        initPlayRecord();
    }


    /**
     * 开始录音
     */
    public void startRecording(){
        /**
         *  还没准备
         *  AudioRecord.STATE_UNINITIALIZED ;
         *
         *  准备完成
         *  AudioRecord.STATE_INITIALIZED
         *
         *  开始采集之后,状态自动变为
         *  AudioRecord.RECORDSTATE_RECORDING
         *
         *  停止采集时调用mAudioRecord.stop()停止录音。
         *  AudioRecord.RECORDSTATE_STOPPED
         *
         */
        if (AudioRecord.ERROR_BAD_VALUE == bufferSizeInBytes || AudioRecord.ERROR == bufferSizeInBytes) {
            throw new RuntimeException("Unable to getMinBufferSize");
        }

        //bufferSizeInBytes is available...
        //或者检测AudioRecord是否确保了获得适当的硬件资源。
        if (audioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
            throw new RuntimeException("The AudioRecord is not uninitialized");
        }


        if (audioRecord.getState()==AudioRecord.STATE_INITIALIZED){
            new Thread(new RecorderRunnable()).start();
        }
    }





    public class RecorderRunnable implements Runnable{

        @Override
        public void run() {

            isRecording = true ;

            try {
                String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath();
                File file=new File(rootPath,System.currentTimeMillis()+".pcm");

                if (!file.exists()){
                    file.createNewFile();
                }

                /*开始录音*/
                audioRecord.startRecording();

                byte[] bytes = new byte[bufferSizeInBytes];
                /*缓存输入流*/
                BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(new FileOutputStream(file));
                DataOutputStream outputStream=new DataOutputStream(bufferedOutputStream);

                while (isRecording && audioRecord.getState() == AudioRecord.RECORDSTATE_RECORDING){
                    if (!isPause){//录音开关
                        /*如果处于录音状态  那么不断的读取录音结果*/
                        int bufferReadResult= audioRecord.read(bytes, 0 ,bufferSizeInBytes);
                        for (int i = 0; i < bufferReadResult; i++) {
                            outputStream.write(bytes[i]);
                        }
                    }
                }



                /*转换成wav*/
                PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(sampleRateInHz,channelConfig,audioFormat);
                /*转换后文件*/
                File outFile=new File(rootPath,System.currentTimeMillis()+".wav");
                pcmToWavUtil.pcmToWav(file.getAbsolutePath(),outFile.getAbsolutePath());

                /*播放文件地址*/
                playFilePath  =  outFile.getAbsolutePath() ;

                bufferedOutputStream.close();
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 是否暂停开关
     */
    private boolean isPause;



    /**
     * 暂停
     */
    public void pause(){
        isPause = true ;
    }


    /**
     * 恢复
     */
    public void resume(){
        isPause = false ;
    }


    /**
     * 停止录音
     */
    public void stopRecord() {
        isRecording = false;
        //停止录音,回收AudioRecord对象,释放内存
        if (audioRecord != null) {
            audioRecord.stop();
            audioRecord.release();
            audioRecord = null;
        }
    }



    AudioTrack mAudioTrack;
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public void initPlayRecord(){
        AudioFormat audioF=new AudioFormat.Builder()
                .setSampleRate(sampleRateInHz)
                .setEncoding(audioFormat)
                .setChannelMask(channelConfig)
                .build();
        AudioAttributes mAudioAttributes=new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build();
        mAudioTrack=new AudioTrack(mAudioAttributes,audioF,bufferSizeInBytes, AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE);
    }



    /**
     * 播放录音
     */

    String playFilePath ;
    public void playRecord(Context context){
        if (TextUtils.isEmpty(playFilePath)){
            Toast.makeText(context,"播放音频文件不存在!",Toast.LENGTH_LONG).show();
            return;
        }
        mAudioTrack.play();
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    FileInputStream fileInputStream=new FileInputStream(playFilePath);
                    byte[] tempBuffer=new byte[bufferSizeInBytes];
                    while (fileInputStream.available()>0){
                        int readCount= fileInputStream.read(tempBuffer);
                        if (readCount == AudioTrack.ERROR_INVALID_OPERATION||readCount==AudioTrack.ERROR_BAD_VALUE){
                            continue;
                        }
                        if (readCount!=0&&readCount!=-1){
                            mAudioTrack.write(tempBuffer,0,readCount);
                        }
                    }
                    Log.e("TAG","end");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

给音频文件添加头部信息,并且转换格式成wav

音频录制完成后,这个时候去存储目录找到音频文件部分,会提示无法播放文件.其实是因为没有加入音频头部信息.一般通过麦克风采集的录音数据都是PCM格式的,即不包含头部信息,播放器无法知道音频采样率、位宽等参数,导致无法播放,显然是非常不方便的。pcm转换成wav,我们只需要在pcm的文件起始位置加上至少44个字节的WAV头信息即可


偏移地址   命名       内容
00-03   ChunkId       "RIFF"
04-07   ChunkSize      下个地址开始到文件尾的总字节数(此Chunk的数据大小)
08-11   fccType       "WAVE"
12-15   SubChunkId1       "fmt ",最后一位空格。
16-19   SubChunkSize1    一般为16,表示fmt Chunk的数据块大小为16字节
20-21   FormatTag      1:表示是PCM 编码
22-23   Channels         声道数,单声道为1,双声道为2
24-27   SamplesPerSec      采样率
28-31   BytesPerSec     码率 :采样率 * 采样位数 * 声道个数,bytePerSecond = sampleRate * (bitsPerSample / 8) * channels
32-33   BlockAlign       每次采样的大小:位宽*声道数/8
34-35   BitsPerSample     位宽
36-39   SubChunkId2     "data"
40-43   SubChunkSize2      音频数据的长度
44-...   data         音频数据

写入头部信息的工具类
PcmToWavUtil .class

public class PcmToWavUtil {
    private static final String TAG = "PcmToWavUtil";

    /**
     * 缓存的音频大小
     */
    private int mBufferSize;
    /**
     * 采样率
     */
    private int mSampleRate;
    /**
     * 声道数
     */
    private int mChannel;


    /**
     * @param sampleRate sample rate、采样率
     * @param channel channel、声道
     * @param encoding Audio data format、音频格式
     */
    PcmToWavUtil(int sampleRate, int channel, int encoding) {
        this.mSampleRate = sampleRate;
        this.mChannel = channel;
        this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
    }


    /**
     * pcm文件转wav文件
     *
     * @param inFilename 源文件路径
     * @param outFilename 目标文件路径
     */
    public void pcmToWav(String inFilename, String outFilename) {
        FileInputStream in;
        FileOutputStream out;
        long totalAudioLen;//总录音长度
        long totalDataLen;//总数据长度
        long longSampleRate = mSampleRate;
        int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
        long byteRate = 16 * mSampleRate * channels / 8;
        byte[] data = new byte[mBufferSize];
        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;

            writeWaveFileHeader(out, totalAudioLen, totalDataLen,
                    longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
                out.flush();

            }
            Log.e(TAG, "pcmToWav: 停止处理");
            in.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 加入wav文件头
     */
    private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
                                     long totalDataLen, long longSampleRate, int channels, long byteRate)
            throws IOException {
        byte[] header = new byte[44];
        // RIFF/WAVE header
        header[0] = 'R';
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        //WAVE
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        // 'fmt ' chunk
        header[12] = 'f';
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        // 4 bytes: size of 'fmt ' chunk
        header[16] = 16;
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        // format = 1
        header[20] = 1;
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 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);
        // block align
        header[32] = (byte) (2 * 16 / 8);
        header[33] = 0;
        // bits per sample
        header[34] = 16;
        header[35] = 0;
        //data
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 0, 44);
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!