前言
本文在《音视频学习: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; } }
参考文章
历史上的今天
暂无评论...
随机推荐
Android设备,当旋转旋钮时布局被绿色边框问题
前言部分Android设备是存在旋钮功能,大多是发送的是KeyEvent.KEYCODE_DPAD_UP和KeyEvent.KEYCODE_DPAD_DOWN。 /** Key code constant: Directional Pad Up key. * May also ...
尼采:孩子和婚姻
兄弟,我单独问你一个问题,这个问题就如探测之铅坠,我可据此了解你的灵魂。你年轻,渴望孩子与婚姻,但我问你,你有资格渴求孩子吗?你是胜利者吗?你战胜自己了吗?你能控制感情吗?你可以主宰你的美德吗?你的渴望是动物般的需要呢,还是孤独或内心冲突的表现?我希望你拥有胜利和自由后再渴望孩子,你应当为自己的...
梁实秋:时间即生命
最令人怵目惊心的一件事,是看着钟表上的秒针一下一下的移动,每移动一下就是表示我们的寿命已经缩短了一部分。再看看墙上挂着的可以一张张撕下的日历,每天撕下一张就是表示我们的寿命又缩短了一天。因为时间即生命。没有人不爱惜他的生命,但很少人珍视他的时间。如果想在有生之年做一点什么事,学一点什么学问,充实自己...
Android Button 字母自动变大写记录
前言开发中,Button控件的Text 自动转为大写字母,这里记录一下,方便自己查询。好记性不如烂笔头正文字母自动变大写的原因只要我们用的Theme是Material或API Level 21+的默认 Theme,Button上的Text默认就是大写。解决方式第一种方法在Xml中的...
刘瑜:适应孤独,就像适应一种残疾
前两天有个网友给我写信,问我如何克服寂寞。她跟我刚来美国时一样,英文不够好,朋友少,一个人等着天亮,一个人等着天黑。“每天学校、家、图书馆、健身房,几点一线。”我说我没什么好招儿,因为我从来就没有克服过这个问题。这些年来我学会的,就是适应它。正如有人所言:“适应孤独,就像适应一种残疾。”我觉得,...
Vim模式切换命令
前言简单记录一下 Vim命令的 AIO(或aio)的使用正文a和A的命令使用a或A是append(追加)的缩写,进入编辑状态。a : 在[光标位置之后的一个位置]开始操作A : 在[光标位置的行尾]开始开始操作i和I的命令使用i或I是 insert(插入)的缩写,进入编辑状态。...