前言
本文在《音视频学习:AudioRecord的简单使用》基础上录制成PCM文件以及转换成WAV文件。
正文
在前一篇基础上,新增录制数据回调以及保存成PCM,最后把PCM文件添加WAV头文件转出WAV文件。
PS: 部分文件前面有附上代码,这里就省略
IRecordBufferListener.java
public interface IRecordBufferListener {
void onRecordBuffer(byte buffer[]);
}
RecordUtils.java
public class RecordUtils {
private static String TAG = MyApp.TAG + RecordUtils.class.getSimpleName();
private static final int DEFAULT_SOURCE = MediaRecorder.AudioSource.MIC; //麦克风
private static final int DEFAULT_RATE = 44100; //采样率
private static final int DEFAULT_CHANNEL = AudioFormat.CHANNEL_IN_STEREO; //双通道(左右声道)
private static final int DEFAULT_FORMAT = AudioFormat.ENCODING_PCM_16BIT; //数据位宽16位
private static AudioRecord mAudioRecord = null;
private static int mMinBufferSize = 0;
private static boolean isRecording = false;
private static RecordThread mRecordThread = null;
private static IRecordBufferListener mIRecordBufferListener = null;
/**
* start record
*/
public static void startRecord() {
mMinBufferSize = AudioRecord.getMinBufferSize(DEFAULT_RATE, DEFAULT_CHANNEL, DEFAULT_FORMAT);
Log.d(TAG, "startRecord mMinBufferSize : " + mMinBufferSize);
if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {
Log.d(TAG, "startRecord error 1 ");
return;
}
mAudioRecord = new AudioRecord(DEFAULT_SOURCE, DEFAULT_RATE, DEFAULT_CHANNEL,
DEFAULT_FORMAT, mMinBufferSize);
if (null == mAudioRecord || mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
Log.d(TAG, "startRecord error 2 ");
return;
}
//启动录音
mAudioRecord.startRecording();
//设置录音标志位
isRecording = true;
//使用线程读取录音数据
mRecordThread = new RecordThread();
mRecordThread.start();
return;
}
/**
* stop record
*/
public static void stopRecord() {
Log.d(TAG, "stopRecord mAudioRecord : " + mAudioRecord);
isRecording = false;
//暂停录音线程
if (null != mRecordThread) {
try {
mRecordThread.join();
mRecordThread = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//停止录音
if (null != mAudioRecord) {
Log.d(TAG, "stopRecord getRecordingState() : " + mAudioRecord.getRecordingState());
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
Log.d(TAG, "stopRecord: ");
}
return;
}
/**
* 录音线程
*/
private static class RecordThread extends Thread {
@Override
public void run() {
super.run();
byte[] buffer = null;
while (isRecording) {
buffer = new byte[mMinBufferSize];
int result = mAudioRecord.read(buffer, 0, buffer.length);
Log.d(TAG, "startRecord result : " + result + " isRecording : " + isRecording);
if (result != AudioRecord.ERROR_INVALID_OPERATION && null != mIRecordBufferListener) {
//录音数据回调
mIRecordBufferListener.onRecordBuffer(buffer);
}
}
}
}
public static void setIRecordBufferListener(IRecordBufferListener listener) {
mIRecordBufferListener = listener;
return;
}
}
Pcm2WavUtils.java
public class Pcm2WavUtils {
private static String TAG = MyApp.TAG + Pcm2WavUtils.class.getSimpleName();
/**
* PCM文件转WAV文件
*
* @param pcmFilePath 输入PCM文件路径
* @param wavFilePath 输出WAV文件路径
* @param sampleRate 采样率,例如44100
* @param channels 声道数 单声道:1或双声道:2
* @param bitNum 采样位数,8或16
*/
public static void convertPcm2Wav(String pcmFilePath, String wavFilePath, int sampleRate,
int channels, int bitNum) {
if (TextUtils.isEmpty(pcmFilePath) || TextUtils.isEmpty(wavFilePath)) {
Log.d(TAG, "convertPcm2Wav null : ");
return;
}
FileInputStream mFileInputStream = null;
FileOutputStream mFileOutputStream = null;
byte[] data = new byte[1024];
try {
//采样字节byte率
long byteRate = sampleRate * channels * bitNum / 8;
mFileInputStream = new FileInputStream(pcmFilePath);
mFileOutputStream = new FileOutputStream(wavFilePath);
//PCM文件大小
long totalAudioLen = mFileInputStream.getChannel().size();
//总大小,由于不包括RIFF和WAV,所以是44 - 8 = 36,在加上PCM文件大小
long totalDataLen = totalAudioLen + 36;
writeWaveFileHeader(mFileOutputStream, totalAudioLen, totalDataLen, sampleRate, channels, byteRate);
int length = 0;
while ((length = mFileInputStream.read(data)) > 0) {
mFileOutputStream.write(data, 0, length);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (mFileInputStream != null) {
try {
mFileInputStream.close();
mFileInputStream = null;
} catch (IOException e) {
e.printStackTrace();
}
}
if (mFileOutputStream != null) {
try {
mFileOutputStream.close();
mFileOutputStream = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
return;
}
/**
* 输出WAV文件
*
* @param out WAV输出文件流
* @param totalAudioLen 整个音频PCM数据大小
* @param totalDataLen 整个数据大小
* @param sampleRate 采样率
* @param channels 声道数
* @param byteRate 采样字节byte率
* @throws IOException
*/
private static void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, int sampleRate, int channels, long byteRate) throws IOException {
if (null == out) {
Log.d(TAG, "writeWaveFileHeader null: ");
}
byte[] header = new byte[44];
header[0] = 'R'; // RIFF
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';//WAVE
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
//FMT Chunk
header[12] = 'f'; // 'fmt '
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;
//编码方式 10H为PCM编码格式
header[20] = 1; // format = 1
header[21] = 0;
//通道数
header[22] = (byte) channels;
header[23] = 0;
//采样率,每个通道的播放速度
header[24] = (byte) (sampleRate & 0xff);
header[25] = (byte) ((sampleRate >> 8) & 0xff);
header[26] = (byte) ((sampleRate >> 16) & 0xff);
header[27] = (byte) ((sampleRate >> 24) & 0xff);
//音频数据传送速率,采样率*通道数*采样深度/8
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) (channels * 16 / 8);
header[33] = 0;
//每个样本的数据位数
header[34] = 16;
header[35] = 0;
//Data chunk
header[36] = 'd';//data
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);
return;
}
}
AudioRecordActivity.java
public class AudioRecordActivity extends AppCompatActivity implements Handler.Callback, View.OnClickListener, IRecordBufferListener {
private String TAG = MyApp.TAG + getClass().getSimpleName();
private int bt_ids[] = new int[]{R.id.at_bt_start_recode, R.id.at_bt_stop_recode, R.id.at_bt_change_to_wav};
private Handler mThreadHandler = null;
private HandlerThread mHandlerThread = null;
private final int MSG_START_RECORD = 0x1000;
private final int MSG_STOP_RECORD = 0x1001;
private final int MSG_UPDATE_RECORD_BUFFER = 0x1002;
private final int MSG_PCM_TO_WAV = 0x1003;
private String mPcmFilePath = null;
private String mWavFilePath = null;
private FileOutputStream mFileOutputStream = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audiorecord);
initUI();
initData();
startHandler();
PermissionUtils.requestPermission(this, 2);
Log.d(TAG, "onCreate: ");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume: ");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause: ");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop: ");
}
@Override
protected void onDestroy() {
super.onDestroy();
closeFileOutputStream();
RecordUtils.setIRecordBufferListener(null);
stopHandler();
Log.d(TAG, "onDestroy: ");
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Log.d(TAG, "onRequestPermissionsResult requestCode : " + requestCode);
if (requestCode == 2) {
for (int i = 0; i < permissions.length; i++) {
Log.d(TAG, "onRequestPermissionsResult : " + permissions[i] + " grantResults : " + grantResults[i]);
}
}
}
@Override
public boolean handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MSG_START_RECORD:
Log.d(TAG, "MSG_START_RECORD : ");
initFileOutputStream();
RecordUtils.startRecord();
break;
case MSG_STOP_RECORD:
Log.d(TAG, "MSG_STOP_RECORD : ");
RecordUtils.stopRecord();
closeFileOutputStream();
break;
case MSG_UPDATE_RECORD_BUFFER:
Log.d(TAG, "MSG_UPDATE_RECORD_BUFFER");
writePcmFile((byte[]) msg.obj);
break;
case MSG_PCM_TO_WAV:
Pcm2WavUtils.convertPcm2Wav(mPcmFilePath, mWavFilePath, 44100, 2, 16);
break;
}
return false;
}
/**
* init ui
*/
private void initUI() {
for (int i = 0; i < bt_ids.length; i++) {
findViewById(bt_ids[i]).setOnClickListener(this);
}
return;
}
private void initData() {
mPcmFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/125la.pcm";
mWavFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/125la.wav";
Log.d(TAG, "initData mPcmFilePath : " + mPcmFilePath);
Log.d(TAG, "initData mWavFilePath : " + mWavFilePath);
RecordUtils.setIRecordBufferListener(this);
return;
}
/**
* start handler
*/
private void startHandler() {
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mThreadHandler = new Handler(mHandlerThread.getLooper(), this);
return;
}
/**
* stop handler
*/
private void stopHandler() {
if (null != mThreadHandler) {
mThreadHandler.removeCallbacksAndMessages(null);
}
if (null != mHandlerThread) {
mHandlerThread.quitSafely();
}
return;
}
@Override
public void onClick(View v) {
Log.d(TAG, "onClick : " + v.toString());
switch (v.getId()) {
case R.id.at_bt_start_recode:
mThreadHandler.sendEmptyMessage(MSG_START_RECORD);
break;
case R.id.at_bt_stop_recode:
mThreadHandler.sendEmptyMessage(MSG_STOP_RECORD);
break;
case R.id.at_bt_change_to_wav:
mThreadHandler.sendEmptyMessage(MSG_PCM_TO_WAV);
break;
}
}
@Override
public void onRecordBuffer(byte[] buffer) {
Message message = mThreadHandler.obtainMessage();
message.obj = buffer;
message.what = MSG_UPDATE_RECORD_BUFFER;
mThreadHandler.sendMessage(message);
}
/**
* @param buffer
*/
private void writePcmFile(byte[] buffer) {
Log.d(TAG, "writePcmFile : ");
if (null != mFileOutputStream) {
try {
mFileOutputStream.write(buffer);
} catch (IOException e) {
e.printStackTrace();
closeFileOutputStream();
}
}
return;
}
/**
* init FileOutputStream
*/
private void initFileOutputStream() {
try {
mFileOutputStream = new FileOutputStream(mPcmFilePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return;
}
/**
* close FileOutputStream
*/
private void closeFileOutputStream() {
if (null != mFileOutputStream) {
try {
mFileOutputStream.close();
mFileOutputStream = null;
} catch (IOException e) {
e.printStackTrace();
}
}
return;
}
}
