安卓实现PCM音频的录制和播放
最近刚接触音视频,想把学到的一些东西记录下来。
本文主要是使用AudioRecord写了一个音频捕获类AudioCapturer,录制的时候在根目录上存储了一个audio-record.pcm的文件。
AudioCapturer.java
public class AudioCapturer {
private static final String TAG = "AudioCapturer";
private static final int DEFAULT_SOURCE = MediaRecorder.AudioSource.MIC;
private static final int DEFAULT_SAMPLE_RATE = 44100;
private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
private AudioRecord mAudioRecord;
private int mMinBufferSize = 0;
private Thread mCaptureThread;
private boolean mIsCaptureStarted = false;
private volatile boolean mIsLoopExit = false;
private OnAudioFrameCapturedListener mAudioFrameCapturedListener;
public interface OnAudioFrameCapturedListener {
public void onAudioFrameCaptured(byte[] audioData);
}
public boolean isCaptureStarted() {
return mIsCaptureStarted;
}
public void setOnAudioFrameCapturedListener(OnAudioFrameCapturedListener listener) {
mAudioFrameCapturedListener = listener;
}
public boolean startCapture() {
return startCapture(DEFAULT_SOURCE, DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_CONFIG,
DEFAULT_AUDIO_FORMAT);
}
public boolean startCapture(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {
if (mIsCaptureStarted) {
Log.e(TAG, "Capture already started !");
return false;
}
mMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat);
if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {
Log.e(TAG, "Invalid parameter !");
return false;
}
Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !");
mAudioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,mMinBufferSize);
if (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
Log.e(TAG, "AudioRecord initialize fail !");
return false;
}
mAudioRecord.startRecording();
mIsLoopExit = false;
mCaptureThread = new Thread(new AudioCaptureRunnable());
mCaptureThread.start();
mIsCaptureStarted = true;
Log.d(TAG, "Start audio capture success !");
return true;
}
public void stopCapture() {
if (!mIsCaptureStarted) {
return;
}
mIsLoopExit = true;
try {
mCaptureThread.interrupt();
mCaptureThread.join(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
mAudioRecord.stop();
}
mAudioRecord.release();
mIsCaptureStarted = false;
mAudioFrameCapturedListener = null;
Log.d(TAG, "Stop audio capture success !");
}
File saveFile = new File(Environment.getExternalStorageDirectory(), "audio-record.pcm");
DataOutputStream dataOutputStream;
{
try {
dataOutputStream = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(saveFile)));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private class AudioCaptureRunnable implements Runnable {
@Override
public void run() {
while (!mIsLoopExit) {
byte[] buffer = new byte[mMinBufferSize];
int ret = mAudioRecord.read(buffer, 0, mMinBufferSize);
if (ret == AudioRecord.ERROR_INVALID_OPERATION) {
Log.e(TAG , "Error ERROR_INVALID_OPERATION");
}
else if (ret == AudioRecord.ERROR_BAD_VALUE) {
Log.e(TAG , "Error ERROR_BAD_VALUE");
}
else {
if (mAudioFrameCapturedListener != null) {
mAudioFrameCapturedListener.onAudioFrameCaptured(buffer);
}
try {
dataOutputStream.write(buffer,0,mMinBufferSize);
} catch (IOException e) {
e.printStackTrace();
}
Log.d(TAG , "OK, Captured "+ret+" bytes !");
}
SystemClock.sleep(10);
}
}
}
}
在Activity里可以直接调用startCapture()和stopCapture()就可以录制音频了。
播放的话直接使用这个方法就可以:
//播放音频(PCM)
public void play()
{
DataInputStream dis=null;
String filename = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "audio-record.pcm";
Log.i("TAG", "filename: "+filename);
try {
//从音频文件中读取声音
dis=new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//最小缓存区
int bufferSizeInBytes= AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_MONO,AudioFormat.ENCODING_PCM_16BIT);
//创建AudioTrack对象 依次传入 :流类型、采样率(与采集的要一致)、音频通道(采集是IN 播放时OUT)、量化位数、最小缓冲区、模式
player=new AudioTrack(AudioManager.STREAM_MUSIC,44100,AudioFormat.CHANNEL_OUT_MONO,AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes, AudioTrack.MODE_STREAM);
byte[] data =new byte [bufferSizeInBytes];
player.play();//开始播放
while(true)
{
int i=0;
try {
assert dis != null;
while(dis.available()>0&&i<data.length)
{
data[i]=dis.readByte();//录音时write Byte 那么读取时就该为readByte要相互对应
i++;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
player.write(data,0,data.length);
if(i!=bufferSizeInBytes) //表示读取完了
{
player.stop();//停止播放
player.release();//释放资源
break;
}
}
}
简单的录制和播放就实现了,如果有需要的话可以添加一些比如录制时间、进度条等控件来使音频可视化。