Android 音视频开发【音频篇】【二】音频采样

1,489 阅读3分钟

音频采样,也可以说是音频录制,本篇通过介绍AudioTrack的使用方法,来对麦克风进行音频采样

一、音频录制

1.1 MediaRecorder

Android开发中,通常会使用MediaRecorder来对音频进行录制,并最终得到一个编码好的音频文件(一般是录制成AAC编码格式)

MediaRecorder的使用流程一般是:

  • 构建MediaRecorder实例

    MediaRecorder mediaRecorder = new MediaRecorder()

  • 设置录制声源(一般是设置为麦克风

    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)

  • 设置编码格式

    mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)

  • 设置输出格式

    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS)

  • 设置录制路径

    mediaRecorder.setOutputFile(path)

  • 准备

    mediaRecorder.prepare()

  • 开始

    mediaRecorder.start()

  • 结束

    mediaRecorder.stop()

可以看到,只需要传入必要的参数,就可以得到一个能在任何支持AAC格式的设备上播放该音频文件,不过,这不是本篇文章的重点,本篇文章的重点是介绍如何录制音频的原始格式,即录制Pcm格式的音频文件

1.2 AudioRecord

录制Pcm格式的音频文件,使用的是AudioRecord,该组件可能在一般的Android开发中很少会用到,不过想要录制Pcm格式的音频文件,一个在应用层的选择,就是AudioRecord

AudioRecord的使用分为以下几个部分

  • 开启子线程
  • 构建实例
  • 开始录制
  • 循环从AudioRecord读取数据,写入文件
  • 停止录制,释放资源

下面会详细介绍AudioRecord的录制流程

二、AudioRecord录制流程

2.1 开启子线程

因为后面需要有一个循环不断的读取和写入数据,故将录制相关操作放入子线程中

private static class RecordThread extends Thread {
    public RecordThread() {}
}

2.2 配置必要的参数

private static class RecordThread extends Thread {
    /**
     * pcm录制组件
     */
    private AudioRecord audioRecord;
    /**
     * 文件输出
     */
    private FileOutputStream fos;
    private final String path;
    /**
     * 声源(一般是来自麦克风)
     */
    private final int audioSource;
    /**
     * 采样率
     */
    private final int sampleRateInHz;
    /**
     * 声道设置
     */
    private final int channelConfig;
    /**
     * 编码格式
     */
    private final int audioFormat;
    /**
     * 音频缓存buffer
     */
    private int bufferSizeInByte;
    /**
     * 是否停止录制
     */
    private boolean isStopRecord = false;
    /**
     * 构造方法(传入必要的参数)
     */
    public RecordThread(String path,
                        int audioSource,
                        int sampleRateInHz,
                        int channelConfig,
                        int audioFormat
    ) {
        this.path = path;
        this.audioSource = audioSource;
        this.sampleRateInHz = sampleRateInHz;
        this.channelConfig = channelConfig;
        this.audioFormat = audioFormat;
    }
}

构建AudioRecord需要以下参数

  • audioSource

    声源,一般写麦克风MediaRecorder.AudioSource.MIC

  • sampleRateInHz

    采样率,通常会使用44100

  • channelConfig

    声道设置,比如单声道CHANNEL_IN_MONO,双声道CHANNEL_IN_STEREO

  • audioFormat

    编码格式,通常可以选择ENCODING_PCM_8BIT,也可以选择ENCODING_PCM_16BIT

  • bufferSizeInByte

    该参数表示的是音频缓存的Buffer字节数,用于读取AudioRecord数据,它是通过AudioRecord.getMinBufferSize方法获取

2.3 初始化

在写完构造方法后,可以重写Threadrun方法

# run()

@Override
public void run() {
    super.run();
    initIo();
    initAudioRecord();
    record();
}

run方法里面调用了三个方法,分别是

  • initIo()

    因为是写入文件,所以需要初始化文件IO

  • initAudioRecord()

    根据构造方法传入的参数,构建AudioRecord

  • record()

    开始真正的录制

# initIo()

/**
 * 初始化IO
 */
private void initIo() {
    if (TextUtils.isEmpty(path)) {
        return;
    }
    File file = new File(path);
    if (file.exists()) {
        file.delete();
    }
    try {
        fos = new FileOutputStream(path);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        fos = null;
    }
}

# initAudioRecord()

/**
 * 初始化pcm录制组件
 */
private void initAudioRecord() {
    bufferSizeInByte = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
    audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInByte);
}

2.4 录制

进入到record()方法时,就开始真正的录制

# record()

/**
 * 开始录制
 */
private void record() {
    if (audioRecord == null || fos == null) {
        return;
    }
    byte[] data = new byte[bufferSizeInByte];
    audioRecord.startRecording();
    for (; ; ) {
        if (isStopRecord) {
            release();
            break;
        }
        int readSize = audioRecord.read(data, 0, bufferSizeInByte);
        if (readSize <= 0) {
            isStopRecord = true;
            continue;
        }
        try {
            fos.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

record()方法中,构建了一个for(;;)形式的死循环,然后在内部会有一个isStopRecord的标志位来调用release()方法,并退出循环

在死循环里,每次都会调用audioRecord.read()方法读取音频数据,接着将数据写入对应的文件中

等手动结束,或者是读取到的数据小于等于0时,则释放资源

# release()

/**
 * 释放资源
 */
private void release() {
    if (audioRecord != null) {
        audioRecord.stop();
        audioRecord.release();
        audioRecord = null;
    }
    if (fos != null) {
        try {
            fos.close();
            fos = null;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这一流程结束,即得到录制好了Pcm文件,不过,一般的播放器是没办法播放该文件,那么下一篇文章将介绍如何播放Pcm格式的音频文件

三、GitHub

相关的类均可在GitHub中找到

PcmRecorder.java

PcmActivity.java