记一组关于录音音频相关的工具类

733 阅读3分钟

监听器

监听录音开始,录音结束,音量改变的监听器。


public interface AudioRecorderListener {
    void onRecordStart();
    void onVolumeChanged(double volume);
    void onRecordFinished(String path,long length);
}

音频录制管理器

可以指定配置音频格式,麦克风,采样率声道等相关格式。并对原始格式进行相应转换,同时还可以回调音频音量大小

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log;

import androidx.annotation.RequiresApi;

import com.koolearn.ieltspro2019.utils.function.PreferencesUtil;

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 科大讯飞需要的特殊格式音频管理类
 * 音量获取相关
 * https://www.cnblogs.com/renhui/p/11704635.html
 *
 * 音频录制相关
 * https://blog.csdn.net/u011932309/article/details/103852669
 */
public class AudioRecorderManager {
    private boolean isRecording ;
    //指的是麦克风
    private int audioSource = MediaRecorder.AudioSource.MIC;
    //指定采样率 (MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100,目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)
    private int sampleRateInHz = 16000;
    //指定捕获音频的声道数目。在AudioFormat类中指定用于此的常量  (单声道CHANNEL_IN_MONO就是一个喇叭 ,双声道CHANNEL_IN_STEREO两个喇叭 )
    private int channelConfig = AudioFormat.CHANNEL_IN_MONO;
    //指定音频量化位数 ,在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 ;

    private AudioRecorderListener listener;

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



    private AudioRecorderManager(){
        /*实例化音频捕获的实例*/
        audioRecord = initAudioRecord();
//        initPlayRecord();
    }

    private AudioRecord initAudioRecord(){
        if(audioRecord==null){
            audioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,bufferSizeInBytes);
        }
        return audioRecord;
    }


    public void setListener(AudioRecorderListener listener) {
        this.listener = listener;
    }

    /**
     * 开始录音
     */
    public void startRecording(){
        /**
         *  还没准备
         *  AudioRecord.STATE_UNINITIALIZED ;
         *
         *  准备完成
         *  AudioRecord.STATE_INITIALIZED
         *
         *  开始采集之后,状态自动变为
         *  AudioRecord.RECORDSTATE_RECORDING
         *
         *  停止采集时调用mAudioRecord.stop()停止录音。
         *  AudioRecord.RECORDSTATE_STOPPED
         *
         */
        initAudioRecord();
        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(listener)).start();
        }
    }

    public class RecorderRunnable implements Runnable{
        private AudioRecorderListener listener;
        public RecorderRunnable(AudioRecorderListener listener) {
            this.listener = listener;
        }

        @Override
        public void run() {
            isRecording = true ;
            try {
                String rootPath = PreferencesUtil.getDownLoadRootPath();
                File file=new File(rootPath,"kypl-record.pcm");

                if (file.exists()){
                    file.delete();
                }else{
                    file.createNewFile();
                }

                /*开始录音*/
                audioRecord.startRecording();
                listener.onRecordStart();
                byte[] buffer = new byte[bufferSizeInBytes];
                /*缓存输入流*/
                BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(new FileOutputStream(file));
                DataOutputStream outputStream=new DataOutputStream(bufferedOutputStream);
                long start=System.currentTimeMillis();
                while (isRecording && audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING){
                    if (!isPause){//录音开关
                        /*如果处于录音状态  那么不断的读取录音结果*/
                        int bufferReadResult= audioRecord.read(buffer, 0 ,bufferSizeInBytes);
                        for (int i = 0; i < bufferReadResult; i++) {
                            outputStream.write(buffer[i]);
                        }
                        short[] shorts= byteArray2ShortArray(buffer);
                        long v = 0;
                        // 将 buffer 内容取出。进行平方和运算
                        for (int i = 0; i < shorts.length; i++) {
                            v += shorts[i] * shorts[i];
                        }
                        // 平方和除以数据总长度,得到音量大小。
                        double mean = v / (double) bufferReadResult;
                        double volume = 10 * Math.log10(mean);
                        listener.onVolumeChanged(volume);
                        Log.d("--->", "分贝值 = " + volume + "dB");
                    }
                }
                long length = System.currentTimeMillis()-start;
                /*转换成wav*/
                PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(sampleRateInHz,channelConfig,audioFormat);
                /*转换后文件*/
                File outFile=new File(rootPath,"kypl-record.wav");
                if (outFile.exists()){
                    outFile.delete();
                }
                pcmToWavUtil.pcmToWav(file.getAbsolutePath(),outFile.getAbsolutePath());
                Log.d("--->", "转换后文件地址 = " + outFile.getAbsolutePath());
//                /*播放文件地址*/
//                String playFilePath  =  outFile.getAbsolutePath() ;

                bufferedOutputStream.close();
                outputStream.close();
                listener.onRecordFinished(outFile.getAbsolutePath(),length);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // byte数组转short数组
    public static short[] byteArray2ShortArray(byte[] data) {
        short[] retVal = new short[data.length/2];
        for (int i = 0; i < retVal.length; i++) {
            retVal[i] = (short) ((data[i * 2] & 0xff) | (data[i * 2 + 1] & 0xff) << 8);
        }
        return retVal;
    }

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



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


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


    /**
     * 停止录音
     */
    public void stopRecord() {
        isRecording = false;
        //停止录音,回收AudioRecord对象,释放内存
        if (audioRecord != null) {
            try{//此处捕捉关闭时的异常。
                audioRecord.stop();
                audioRecord.release();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                audioRecord = null;
            }
        }
    }
}

PCM转Wav格式工具类转换器

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.util.Log;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

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);
    }
}