MediaRecorder VS AudioRecorder

输出格式:MediaRecorder录制的数据是 amr MP3 格式
AudioRecorder录制出来的是比较原始的PCM音频格式

优劣:从输出格式上其实就能看出来MediaRecorder更上层一点,也就是用起来会比较简单,不需要音频转码的操作,事实上从下面的代码中也可以看出来,MediaRecorder录制的代码也很简单,包括获取分贝。而AudioRecorder录制出来的PCM格式的音频,通常是需求对音频数据做处理会用到,而且MediaRecorder是可以用来视屏的,AudioRecorder只能录制音频。


效果图

效果有点可惜的就是模拟器没法真正的录音,这里只是做个展示,demo里面是有获取音量和时间的代码的,包括播放音频的代码。

Android音频录制分贝图 手机能录到的分贝_数据

直接上代码

MediaRecorder 录音
public class MediaRecorderUtil {

    public static MediaRecorder recorder;

    public static void startRecordering(String filePath) {
        try {
            if (recorder != null) {
                recorder.release();
                recorder = null;
            }
            recorder = new MediaRecorder();
            // 设置麦克风为音频源
            recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            // 设置音频文件的编码
            recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
            // 设置输出文件的格式
            recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);//可以设置成 MediaRecorder.AudioEncoder.AMR_NB

            recorder.setOutputFile(filePath);

            recorder.prepare();
            recorder.start();
        } catch (Exception e) {
        }
    }

    public static void stopRecordering() {
        if (recorder != null) {
            recorder.stop();
            recorder.release();
            recorder = null;
        }
    }
}

这里很简单,就是一些API的调用,写入文件的操作直接调用 setOutputFile 设置就OK了,我想也没什么好说的了。

包括计算MediaRecorder录音时的分贝

double ratio = MediaRecorderUtil.recorder.getMaxAmplitude();
       if (ratio > 1)
           ratio = 20 * Math.log10(ratio);
       voice.setText("当前音量:" + ratio);

AudioRecorder 录音
package cjh.recorder;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Created by chenjiahuan on 16/5/8.
 */
public class AudioRecorderUtil {

    private static Object mLock = new Object();

    private static boolean first = true;

    private static AudioRecord audioRecord;

    private static String currentFilePath;

    // 音频获取源
    private static int audioSource = MediaRecorder.AudioSource.MIC;
    // 设置音频采样率,44100是目前的标准
    private static int sampleRateInHz = 44100;
    // 设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道
    private static int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
    // 音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。
    private static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    // 缓冲区字节大小
    private static int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,
            channelConfig, audioFormat);

    public static void startRecordering(String filePath) {
        first = true;
        currentFilePath = filePath;
        audioRecord = new AudioRecord(audioSource, sampleRateInHz,
                channelConfig, audioFormat, bufferSizeInBytes);
        audioRecord.startRecording();
        new Thread(new AudioRecordThread()).start();
    }

    public static void stopRecording() {
        if (audioRecord != null) {
            try {
                audioRecord.stop();
                audioRecord.release();
                audioRecord = null;

            } catch (RuntimeException e) {
            }
        }
    }

    static class AudioRecordThread implements Runnable {
        @Override
        public void run() {
            writeDateTOFile();//往文件中写入裸数据
            copyWaveFile(currentFilePath, currentFilePath.replace(".pcm", ".wav"));//给裸数据加上头文件
        }
    }

    // 这里得到可播放的音频文¬件
    private static void copyWaveFile(String inFilename, String outFilename) {
        FileInputStream in = null;
        FileOutputStream out = null;
        long totalAudioLen = 0;
        long totalDataLen = totalAudioLen + 36;
        long longSampleRate = sampleRateInHz;
        int channels = 2;
        long byteRate = 16 * sampleRateInHz * channels / 8;
        byte[] data = new byte[bufferSizeInBytes];
        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);
            }
            in.close();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。
     * 为我为啥插入这44个字节,这个还真没深入研究,不过你随便打开一个wav
     * 音频的文件,可以发现前面的头文件可以说基本一样哦。每种格式的文件都有
     * 自己特有的头文件。
     */
    private static void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen,
                                            long totalDataLen, long longSampleRate, int channels, long byteRate)
            throws IOException {
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF/WAVE header
        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);
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f'; // 'fmt ' chunk
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16; // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 1; // format = 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);
        header[32] = (byte) (2 * 16 / 8); // block align
        header[33] = 0;
        header[34] = 16; // bits per sample
        header[35] = 0;
        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);
    }

    /**
     * 这里将数据写入文件,但是并不能播放,因为AudioRecord获得的音频是原始的裸音频,
     * 如果需要播放就必须加入一些格式或者编码的头信息。但是这样的好处就是你可以对音频的 裸数据进行处理,比如你要做一个爱说话的TOM
     * 猫在这里就进行音频的处理,然后重新封装 所以说这样得到的音频比较容易做一些音频的处理。
     */
    private static void writeDateTOFile() {
        // new一个byte数组用来存一些字节数据,大小为缓冲区大小
        final byte[] audiodata = new byte[bufferSizeInBytes];
        FileOutputStream fos = null;
        try {
            File file = new File(currentFilePath);
            if (file.exists()) {
                file.delete();
            }

            file.createNewFile();

            fos = new FileOutputStream(file);// 建立一个可存取字节的文件
        } catch (Exception e) {
            e.printStackTrace();
        }
        while (MainActivity.instance.recordering == true) {
            final int readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
            if (AudioRecord.ERROR_INVALID_OPERATION != readsize) {
                try {
                    fos.write(audiodata);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            Timer timer = new Timer();
            if (first)
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        long v = 0;
                        // 将 buffer 内容取出,进行平方和运算
                        for (int i = 0; i < audiodata.length; i++) {
                            v += audiodata[i] * audiodata[i];
                        }
                        // 平方和除以数据总长度,得到音量大小。
                        double mean = v / (double) readsize;
                        final double volume = 10 * Math.log10(mean);
                        MainActivity.instance.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                MainActivity.instance.voice.setText(volume + "");
                            }
                        });
                    }
                }, 0, 1000);
            first = false;

        }
        try {
            fos.close();// 关闭写入流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

我把所有代码放在一个类里面,有点乱,只是为了方便看,拆开来一点一点说,先从AudioRecorder的创建开始:

// 音频获取源
    private static int audioSource = MediaRecorder.AudioSource.MIC;
    // 设置音频采样率,44100是目前的标准
    private static int sampleRateInHz = 44100;
    // 设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道
    private static int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
    // 音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。
    private static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    // 缓冲区字节大小
    private static int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,
            channelConfig, audioFormat);

    public static void startRecordering(String filePath) {
        first = true;
        currentFilePath = filePath;
        audioRecord = new AudioRecord(audioSource, sampleRateInHz,
                channelConfig, audioFormat, bufferSizeInBytes);
        audioRecord.startRecording();
        new Thread(new AudioRecordThread()).start();
    }

创建对象的各个参数都写了详细的注释,上面代码中

new Thread(new AudioRecordThread()).start();

这行代码做了很多事

static class AudioRecordThread implements Runnable {
        @Override
        public void run() {
            writeDateTOFile();//往文件中写入裸数据,并计算了音量
            copyWaveFile(currentFilePath, currentFilePath.replace(".pcm", ".wav"));//给裸数据加上头文件
        }
    }

copyWaveFile 这个是从网上copy下来的,对于这个转码我也不是很懂,不敢乱讲

private static void writeDateTOFile() {
        // new一个byte数组用来存一些字节数据,大小为缓冲区大小
        final byte[] audiodata = new byte[bufferSizeInBytes];
        FileOutputStream fos = null;
        try {
            File file = new File(currentFilePath);
            if (file.exists()) {
                file.delete();
            }

            file.createNewFile();

            fos = new FileOutputStream(file);// 建立一个可存取字节的文件
        } catch (Exception e) {
            e.printStackTrace();
        }
        while (MainActivity.instance.recordering == true) {
            final int readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
            if (AudioRecord.ERROR_INVALID_OPERATION != readsize) {
                try {
                    fos.write(audiodata);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            Timer timer = new Timer();
            if (first)
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        long v = 0;
                        // 将 buffer 内容取出,进行平方和运算
                        for (int i = 0; i < audiodata.length; i++) {
                            v += audiodata[i] * audiodata[i];
                        }
                        // 平方和除以数据总长度,得到音量大小。
                        double mean = v / (double) readsize;
                        final double volume = 10 * Math.log10(mean);
                        MainActivity.instance.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                MainActivity.instance.voice.setText(volume + "");
                            }
                        });
                    }
                }, 0, 1000);
            first = false;

        }
        try {
            fos.close();// 关闭写入流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

写入文件的方法其实也很简单,只是我在里面加了一个Timer,定时的去获取音量

Timer timer = new Timer();
            if (first)//first 这里是为了避免在while(true)中开启多个任务,获取音量的任务只需要一个就够了
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        long v = 0;
                        // 将 buffer 内容取出,进行平方和运算
                        for (int i = 0; i < audiodata.length; i++) {
                            v += audiodata[i] * audiodata[i];
                        }
                        // 平方和除以数据总长度,得到音量大小。
                        double mean = v / (double) readsize;
                        final double volume = 10 * Math.log10(mean);
                        MainActivity.instance.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                MainActivity.instance.voice.setText(volume + "");
                            }
                        });
                    }
                }, 0, 1000);
            first = false;