在android sdk
中提供了两种方式来实现音频的采集:MediaRecorder
和AudioRecord
,其中的MediaRecorder
处于更上层,它可以对音频录制的数据编码成AMR
,MP3
等格式,并存储为文件,而AudioRecord
则更灵活,因为它可以录制最原始的PCM流数据
,这个在直播中很常见。
使用不同的方式处理音频,API肯定有所不同,这里暂时先抽象一个接口:
public interface IAudioRecorder {
interface RECORD_STATE {
int STATE_RECORDING = 0;
int STATE_SUCCESS = 1;
int STATE_ERROR = 2;
}
/**
* 初始化
*/
void initRecorder();
/**
* 开始录制音频
*/
int recordStart();
/**
* 停止录制音频
*/
void recordStop();
}
系统都已经给你提供好了的轮子,我们只需要设置几个参数就可以了:
public class MediaRecordRecorder implements IAudioRecorder {
/**
* 基于MediaRecorder录制音频
*/
private MediaRecorder mMediaRecorder;
/**
* 是否正在录制
*/
private boolean isRecord = false;
private String filePath;
public MediaRecordRecorder(String filePath){
this.filePath = filePath;
}
@Override
public void initRecorder() {
//实例化MediaRecorder对象
mMediaRecorder = new MediaRecorder();
//从麦克风采集声音数据
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//设置输出格式为MP4
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//设置采样频率44100 频率越高,音质越好,文件越大
mMediaRecorder.setAudioSamplingRate(44100);
//设置声音数据编码格式,音频通用格式是AAC
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
//设置编码频率
mMediaRecorder.setAudioEncodingBitRate(96000);
//设置输出文件
mMediaRecorder.setOutputFile(filePath);
}
@Override
public int recordStart() {
//判断是否有外部存储设备sdcard
if (isRecord) {
return RECORD_STATE.STATE_RECORDING;
} else {
if (mMediaRecorder != null) {
try {
mMediaRecorder.prepare();
mMediaRecorder.start();
// 让录制状态为true
isRecord = true;
return RECORD_STATE.STATE_SUCCESS;
} catch (IOException e) {
e.printStackTrace();
}
}
return RECORD_STATE.STATE_ERROR;
}
}
@Override
public void recordStop() {
if (mMediaRecorder != null) {
isRecord = false;
mMediaRecorder.stop();
mMediaRecorder.release();
mMediaRecorder = null;
}
}
}
笔者这里只是作演示操作,因此部分参数直接硬编码,实际过程最好以参数的形式对外提供
这种方式需要我们自己在线程中,循环读取音频数据:
public class AudioRecordRecorder implements IAudioRecorder {
private static final String TAG = "AudioRecordRecorder";
private int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
/**
* 音频采样率
*/
public static int SAMPLE_RATE = 44100;
/**
* 单声道
*/
public final static int CHANNEL = AudioFormat.CHANNEL_IN_STEREO;
/**
* 16比特
*/
public final static int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
/**
* 音频录制实例
*/
protected AudioRecord audioRecord;
/**
* 录制线程
*/
private Thread recordThread;
/**
* 输出的文件路径
*/
private String pcmPath;
/**
* 缓冲区大小
*/
private int bufferSize = 0;
/**
* 是否正在录制
*/
private boolean isRecording = false;
/**
* 回调原始的PCM数据
*/
private OnAudioRecordListener mOnAudioRecordListener;
public AudioRecordRecorder(String filePath) {
this.pcmPath = filePath;
}
@Override
public void initRecorder() {
if (null != audioRecord) {
audioRecord.release();
}
try {
bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL, AUDIO_FORMAT);
audioRecord = new AudioRecord(AUDIO_SOURCE, SAMPLE_RATE, CHANNEL, AUDIO_FORMAT, bufferSize);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public int recordStart() {
if (isRecording) {
return RECORD_STATE.STATE_RECORDING;
} else if (audioRecord != null && audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
try {
audioRecord.startRecording();
isRecording = true;
recordThread = new Thread(new AudioRecordRunnable());
recordThread.start();
return RECORD_STATE.STATE_SUCCESS;
} catch (Exception e) {
e.printStackTrace();
}
}
return RECORD_STATE.STATE_ERROR;
}
/**
* 停止音频录制
*/
@Override
public void recordStop() {
try {
if (audioRecord != null) {
isRecording = false;
try {
if (recordThread != null) {
recordThread.join();
recordThread = null;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
//释放资源
recordRelease();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void recordRelease() {
if (audioRecord != null) {
if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
audioRecord.stop();
}
audioRecord.release();
audioRecord = null;
}
}
private class AudioRecordRunnable implements Runnable {
private FileOutputStream outputStream = null;
@Override
public void run() {
try {
if (!TextUtils.isEmpty(pcmPath)) {
outputStream = new FileOutputStream(pcmPath);
}
byte[] audioBuffer = new byte[bufferSize];
while (isRecording && audioRecord != null) {
int audioSampleSize = audioRecord.read(audioBuffer, 0, bufferSize);
if (audioSampleSize > 0) {
if (outputStream != null) {
outputStream.write(audioBuffer);
}
if (mOnAudioRecordListener != null) {
mOnAudioRecordListener.onAudioBuffer(audioBuffer, audioBuffer.length);
}
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
close(outputStream);
outputStream = null;
}
}
}
private void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void setOnAudioRecordListener(OnAudioRecordListener onAudioRecordListener) {
this.mOnAudioRecordListener = onAudioRecordListener;
}
public interface OnAudioRecordListener {
void onAudioBuffer(byte[] buffer, int length);
}
}
新建工程ffmpeg-audio-encode
,在应用的点击事件中处理如下:
/**
* 点击开始录制按钮
* @param view
*/
public void onRecordStart(View view) {
File targetDir = getExternalFilesDir(null);
if (audioRecorder == null) {
audioRecorder = new MediaRecordRecorder(targetDir.getAbsolutePath()+ File.separator+"output.aac");
//audioRecorder = new AudioRecordRecorder(targetDir.getAbsolutePath()+ File.separator+"output.pcm");
}
audioRecorder.initRecorder();
audioRecorder.recordStart();
//更新按钮状态
mBtnStart.setEnabled(false);
mBtnStop.setEnabled(true);
}
/**
* 点击停止按钮
* @param view
*/
public void onRecordStop(View view) {
if (audioRecorder != null) {
audioRecorder.recordStop();
audioRecorder = null;
}
//更新按钮状态
mBtnStart.setEnabled(true);
mBtnStop.setEnabled(false);
}
基于MediaRecorder
方式我们可以直接得到output.aac
文件,这个直接使用系统的音频播放器
即可播放
基于AudioRecord
方式我们可以直接得到output.ocm
文件,需要导出到电脑上,使用ffplay播放器
进行播放
ffplay -ar 44100 -channels 2 -f s16le -i output.pcm
项目地址:ffmpeg-audio-encode
https://github.com/byhook/ffmpeg4android