用AudioRecord录制音频

最近完成了一个需求,录制一段音频,并且在界面上绘制对应的波形图,分享一下我的心路历程

首先,Android提供录制音频的有两个类。MediaRecorder和AudioRecord,这两个类都可以实现录制音频的功能,但又有一定的区别。前者封装度较高,也就意味着使用简单,设置几个对应的录制的参数,设置输出文件的路径,开始录制,结束录制,就可以得到一个音频文件。后者的优势在于,录制的过程中会把read和write的过程暴露出来,我们拿到这些数据,就可以进行一些比较复杂的需求,比如录制的同时把声音的波形图绘制出来

结合上面的分析,实现这个需求我自然要使用AudioRecord,下来我们结合实例谈一谈AudioRecord的使用。

首先,我先概括的总结一下AudioRecord的使用,1.初始化AudioRecord对象,2. 创建一个buffer数组输出文件,3.开始录制,4,结束录制。

下面,我将结合具体的代码,详细的解释一下这四个步骤。

在这里,我还想谈一下代码封装,我们可以把AudioRecord的初始化放在Activity中,但这就意味着相关代码与Activity严重耦合,一方面不利于Activity代码的可读性,另一方面如果另一个Activity需要录音功能,就会需要copy大量相同的代码,所以这里我选择封装一个AudioRecordManager类。

1.初始化AudioRecord对象

private static final int FREQUENCY = 16000;// 设置音频采样率,44100是目前的标准,但是某些设备仍然支持22050,16000,11025
private static final int CHANNELCONGIFIGURATION = AudioFormat.CHANNEL_IN_MONO;// 设置单声道声道
private static final int AUDIOENCODING = AudioFormat.ENCODING_PCM_16BIT;// 音频数据格式:每个样本16位
public final static int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;// 音频获取源
private int recBufSize;// 录音最小buffer大小
private AudioRecord audioRecord;
private void init() {
        recBufSize = AudioRecord.getMinBufferSize(FREQUENCY,
                CHANNELCONGIFIGURATION, AUDIOENCODING);// 录音组件
        audioRecord = new AudioRecord(AUDIO_SOURCE,// 指定音频来源,这里为麦克风
                FREQUENCY, // 16000HZ采样频率
                CHANNELCONGIFIGURATION,// 录制通道
                AUDIOENCODING,// 录制编码格式
                recBufSize);// 录制缓冲区大小 //先修改
    }

这里的代码无需赘述,就是通过设置几个参数来创建一个AudioRecord对象,值得注意的是有一个音频输出文件的格式,我们这里输出的是PCM文件,这是一种非常基础的音频文件,也就是说理论上他可以转成各种我们常用的音频格式,mp3,aac,wav都是可以的,另一方面,由于它非常基础,它本身是不能直接播放的。

2.创建一个buffer数组和输出文件

这里具体代码我就不贴上来了,比较简单,我说一下他们的作用,buffer数组,是用来存储我们每次从micro phone里面读取的数据的,读到了数据之后就写入到我们创建的输出文件中,这样,停止录制的时候,我们就可以得到一个音频文件了。

3.开始录制

开始录制的时候具体做了以下几个步骤

  • 调用audioRecord.startRecording(),开始录制音频
  • 将标识位isRecording设置为true
  • 开启子线程,只要isRecording为true,则不停的调用audioRecord.read方法读取数据
  • 开启另一个子线程,将读取到的数据写入我们上一步中创建的音频文件
  • 从每次read到的数据中抽取一小部分,将这些数据加工之后绘制到界面上

这里之所以开启两个子线程,是为了读操作与写操作的分离,这样绘制界面的时候能够得到更加流程的体验。因为ui是只关心read这个操作的,如果不分离,write这个过程就会阻塞下一次读到的数据的绘制,造成页面不流畅。

4.结束录制

调用audioRecord.stop(),停止实际的录制动作,然后将标识位isRecording设置为false,停掉while循环,也就是结束了不停读取数据的子线程。

最后我把完整的代码放在下面,给大家参考学习

public class AudioRecordManager {
    private static final int FREQUENCY = 16000;// 设置音频采样率,44100是目前的标准,但是某些设备仍然支持22050,16000,11025
    private static final int CHANNELCONGIFIGURATION = AudioFormat.CHANNEL_IN_MONO;// 设置单声道声道
    private static final int AUDIOENCODING = AudioFormat.ENCODING_PCM_16BIT;// 音频数据格式:每个样本16位
    public final static int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;// 音频获取源
    private int recBufSize;// 录音最小buffer大小
    private AudioRecord audioRecord;

    private boolean isRecording;

    private int read;
    private FileOutputStream os;

    public boolean isRecording() {
        return isRecording;
    }

    public void setRecording(boolean recording) {
        isRecording = recording;
    }

    private void init() {
        recBufSize = AudioRecord.getMinBufferSize(FREQUENCY,
                CHANNELCONGIFIGURATION, AUDIOENCODING);// 录音组件
        audioRecord = new AudioRecord(AUDIO_SOURCE,// 指定音频来源,这里为麦克风
                FREQUENCY, // 16000HZ采样频率
                CHANNELCONGIFIGURATION,// 录制通道
                AUDIOENCODING,// 录制编码格式
                recBufSize);// 录制缓冲区大小 //先修改
    }

    public String start(Observer> observer) {
        if (audioRecord == null) {
            init();
        }

        audioRecord.startRecording();
        isRecording = true;

        File dir = new File(Environment.getExternalStorageDirectory());

        if (!dir.exists()) {
            dir.mkdirs();
        }

        String filePath = dir + "/audio_" + System.currentTimeMillis() + ".pcm";

        try {
            os = new FileOutputStream(filePath);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        Observable.create((ObservableOnSubscribe) emitter -> {

            //构造读取数据的数组
            short data[] = new short[recBufSize];

            while (isRecording) {
                //读取数据
                read = audioRecord.read(data, 0, recBufSize);

                if (AudioRecord.ERROR_INVALID_OPERATION != read) {

                    //如果读取的数据有效,发射数据
                    emitter.onNext(data);

                }

            }

            emitter.onComplete();
        }).flatMap(new Function>() {
            @Override
            public ObservableSource apply(short[] shorts) throws Exception {
                return Observable.create(new ObservableOnSubscribe() {
                    @Override
                    public void subscribe(ObservableEmitter e) throws Exception {
                        e.onNext(shorts);

                        byte[] bytes = new byte[read * 2];

                        for (int i = 0; i < read; i++) {
                            byte ss[] = getBytes(shorts[i]);
                            bytes[i * 2] = ss[0];
                            bytes[i * 2 + 1] = ss[1];
                        }

                        os.write(bytes);

                    }
                }).subscribeOn(Schedulers.io());
            }
        }).map(new Function>() {
            @Override
            public List apply(short[] shorts) throws Exception {
                List list = new ArrayList<>();

                for (int i = 0; i < read; i += 500) {
                    list.add(shorts[i]);
                }

                return list;
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(observer);

        return filePath;
    }

    public void stop() {
        if (audioRecord == null)
            return;

        audioRecord.stop();
        isRecording = false;
    }

    public void release() {
        if (audioRecord == null)
            return;

        audioRecord.release();
        audioRecord = null;
    }

    public static byte[] getBytes(short s) {
        byte[] buf = new byte[2];
        for (int i = 0; i < buf.length; i++) {
            buf[i] = (byte) (s & 0x00ff);
            s >>= 8;
        }
        return buf;
    }
}

你可能感兴趣的:(用AudioRecord录制音频)