Android音频处理之通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能
音频这方面很博大精深,我这里肯定讲不了什么高级的东西,最多也只是一些基础类知识,首先,我们要介绍一下android他提供的录音类,实际上他有两个,一个是mediarecorder,还有一个就是我们今天要用到的audiorecord,那他们有什么区别呢?
一.区别
mediarecorder和audiorecord都可以录制音频,区别是mediarecorder录制的音频文件是经过压缩后的,需要设置编码器。并且录制的音频文件可以用系统自带的music播放器播放。
而audiorecord录制的是pcm格式的音频文件,需要用audiotrack来播放,audiotrack更接近底层。
pcm可能更加可以理解为音频的源文件
二.优缺点
audiorecord
主要是实现边录边播以及对音频的实时处理,这个特性让他更适合在语音方面有优势
优点:语音的实时处理,可以用代码实现各种音频的封装
缺点:输出是pcm格式文件,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩
mediarecorder
已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有,aac,amr,3gp等
优点:集成,直接调用相关接口即可,代码量小
缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件
三.准备工作
我们要实现的是一个实时的去录音,播放,停止等功能的测试案例,那我们肯定要准备点什么,比如说,我这里先创建一个项目——pcmsample
然后写个布局
layout_main.xml
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="10dp"> <button android:id="@+id/startaudio" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/button_bg" android:text="开始录音" android:textcolor="@android:color/white"/> <button android:id="@+id/stopaudio" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginbottom="10dp" android:layout_margintop="5dp" android:background="@drawable/button_bg" android:enabled="false" android:text="停止录音" android:textcolor="@android:color/white"/> <button android:id="@+id/playaudio" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/button_bg" android:enabled="false" android:text="播放音频" android:textcolor="@android:color/white"/> <button android:id="@+id/deleteaudio" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="5dp" android:background="@drawable/button_bg" android:text="删除pcm" android:textcolor="@android:color/white"/> <scrollview android:id="@+id/mscrollview" android:layout_width="match_parent" android:layout_height="0dp" android:layout_margintop="5dp" android:layout_weight="1"> <textview android:id="@+id/tv_audio_succeess" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="初始化完成...." android:textcolor="@color/coloraccent"/> </scrollview> </linearlayout>
可以预览一下
这里我给按钮加了一个扁平的效果,实际上写了一个xml,很简单
button_bg.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true"> <shape> <corners android:radius="30dp"/> <solid android:color="@color/colorprimary"/> </shape> </item> <item android:state_pressed="false"> <shape> <corners android:radius="30dp"/> <solid android:color="@color/colorprimarydark"/> </shape> </item> </selector>
好的,回到正题,我们这里有四个按钮,分别是开始。停止,播放,和删除,我们就是要实现这四个功能,在此之前,我们还需要做的事情就是添加权限,因为我们要录音和写内存卡文件,所有需要这两个权限即可
<!--录音--> <uses-permission android:name="android.permission.record_audio" /> <!--读取sd卡--> <uses-permission android:name="android.permission.write_external_storage" />
这里初始化什么的就不说了,我们直接进入正题
四.开始录音
开始录音的话,这里,我们定义一个变量isrecording去控制,这样就比较好结束了,而且要注意的是,录音是不能放在ui线程的,你懂的,所以我们可以写一个开始录音的方法
//开始录音 public void startrecord() { log.i(tag,"开始录音"); //16k采集率 int frequency = 16000; //格式 int channelconfiguration = audioformat.channel_configuration_mono; //16bit int audioencoding = audioformat.encoding_pcm_16bit; //生成pcm文件 file = new file(environment.getexternalstoragedirectory().getabsolutepath() + "/reverseme.pcm"); log.i(tag,"生成文件"); //如果存在,就先删除再创建 if (file.exists()) file.delete(); log.i(tag,"删除文件"); try { file.createnewfile(); log.i(tag,"创建文件"); } catch (ioexception e) { log.i(tag,"未能创建"); throw new illegalstateexception("未能创建" + file.tostring()); } try { //输出流 outputstream os = new fileoutputstream(file); bufferedoutputstream bos = new bufferedoutputstream(os); dataoutputstream dos = new dataoutputstream(bos); int buffersize = audiorecord.getminbuffersize(frequency, channelconfiguration, audioencoding); audiorecord audiorecord = new audiorecord(mediarecorder.audiosource.mic, frequency, channelconfiguration, audioencoding, buffersize); short[] buffer = new short[buffersize]; audiorecord.startrecording(); log.i(tag, "开始录音"); isrecording = true; while (isrecording) { int bufferreadresult = audiorecord.read(buffer, 0, buffersize); for (int i = 0; i < bufferreadresult; i++) { dos.writeshort(buffer[i]); } } audiorecord.stop(); dos.close(); } catch (throwable t) { log.e(tag, "录音失败"); } }
首先,这里我们了解一下采样率,编码,音频流等基本的概念,剩下的大多是读写流的操作了,我们通过创建一个audiorecord去写pcm文件,定义一个while循环,用我们刚才定义的isrecording控制,所以,我们的点击事件就
case r.id.startaudio: thread thread = new thread(new runnable() { @override public void run() { startrecord(); log.e(tag,"start"); } }); thread.start(); printlog("开始录音"); buttonenabled(false, true, false); break;
这里要注意一下thread.start();开启线程,同时打印出log,具体代码如下
//打印log private void printlog(final string resultstring) { tv_audio_succeess.post(new runnable() { @override public void run() { tv_audio_succeess.append(resultstring + "\n"); mscrollview.fullscroll(scrollview.focus_down); } }); }
这里,我为了防止anr,所以控制了一下按钮的焦点
//获取/失去焦点 private void buttonenabled(boolean start, boolean stop, boolean play) { startaudio.setenabled(start); stopaudio.setenabled(stop); playaudio.setenabled(play); }
好的,我们运行一下
看起来没什么变化,但是你去内存卡中就会发现多了一个pcm文件
当然,你只是点击启动录音是不会生成这个pcm文件的,你需要点击停止停止录音的按钮
五.停止录音
停止录音很简单,我们控制通过改变写入流就好了
case r.id.stopaudio: isrecording = false; buttonenabled(true, false, true); printlog("停止录音"); break;
这样才会生成pcm
六播放音频
现在有了pcm我们可以试着去播放了,写一个播放的方法
//播放文件 public void playrecord() { if(file == null){ return; } //读取文件 int musiclength = (int) (file.length() / 2); short[] music = new short[musiclength]; try { inputstream is = new fileinputstream(file); bufferedinputstream bis = new bufferedinputstream(is); datainputstream dis = new datainputstream(bis); int i = 0; while (dis.available() > 0) { music[i] = dis.readshort(); i++; } dis.close(); audiotrack audiotrack = new audiotrack(audiomanager.stream_music, 16000, audioformat.channel_configuration_mono, audioformat.encoding_pcm_16bit, musiclength * 2, audiotrack.mode_stream); audiotrack.play(); audiotrack.write(music, 0, musiclength); audiotrack.stop(); } catch (throwable t) { log.e(tag, "播放失败"); } }
正如上面所说,我们播放需要用到audiotrack,调用他的play方法以及设置一些参数即可
七.删除音频
删除音频只需要删除这个pcm文件就行
//删除文件 private void delefile() { if(file == null){ return; } file.delete(); printlog("文件删除成功"); }
这就是大致的录音逻辑,虽然看起来很简单,但是这正是现在很多语音和音频的最基础部分,特别是语音,如果你从事语音的工作,我相信你会感谢我的!
好了,最后放上完整的代码:
mainactivity
package com.liuguilin.pcmsample; import android.media.audioformat; import android.media.audiomanager; import android.media.audiorecord; import android.media.audiotrack; import android.media.mediarecorder; import android.os.bundle; import android.os.environment; import android.support.v7.app.appcompatactivity; import android.util.log; import android.view.view; import android.widget.button; import android.widget.scrollview; import android.widget.textview; import java.io.bufferedinputstream; import java.io.bufferedoutputstream; import java.io.datainputstream; import java.io.dataoutputstream; import java.io.file; import java.io.fileinputstream; import java.io.fileoutputstream; import java.io.ioexception; import java.io.inputstream; import java.io.outputstream; public class mainactivity extends appcompatactivity implements view.onclicklistener { public static final string tag = "pcmsample"; //是否在录制 private boolean isrecording = false; //开始录音 private button startaudio; //结束录音 private button stopaudio; //播放录音 private button playaudio; //删除文件 private button deleteaudio; private scrollview mscrollview; private textview tv_audio_succeess; //pcm文件 private file file; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); initview(); } //初始化view private void initview() { mscrollview = (scrollview) findviewbyid(r.id.mscrollview); tv_audio_succeess = (textview) findviewbyid(r.id.tv_audio_succeess); printlog("初始化成功"); startaudio = (button) findviewbyid(r.id.startaudio); startaudio.setonclicklistener(this); stopaudio = (button) findviewbyid(r.id.stopaudio); stopaudio.setonclicklistener(this); playaudio = (button) findviewbyid(r.id.playaudio); playaudio.setonclicklistener(this); deleteaudio = (button) findviewbyid(r.id.deleteaudio); deleteaudio.setonclicklistener(this); } //点击事件 @override public void onclick(view v) { switch (v.getid()) { case r.id.startaudio: thread thread = new thread(new runnable() { @override public void run() { startrecord(); log.e(tag,"start"); } }); thread.start(); printlog("开始录音"); buttonenabled(false, true, false); break; case r.id.stopaudio: isrecording = false; buttonenabled(true, false, true); printlog("停止录音"); break; case r.id.playaudio: playrecord(); buttonenabled(true, false, false); printlog("播放录音"); break; case r.id.deleteaudio: delefile(); break; } } //打印log private void printlog(final string resultstring) { tv_audio_succeess.post(new runnable() { @override public void run() { tv_audio_succeess.append(resultstring + "\n"); mscrollview.fullscroll(scrollview.focus_down); } }); } //获取/失去焦点 private void buttonenabled(boolean start, boolean stop, boolean play) { startaudio.setenabled(start); stopaudio.setenabled(stop); playaudio.setenabled(play); } //开始录音 public void startrecord() { log.i(tag,"开始录音"); //16k采集率 int frequency = 16000; //格式 int channelconfiguration = audioformat.channel_configuration_mono; //16bit int audioencoding = audioformat.encoding_pcm_16bit; //生成pcm文件 file = new file(environment.getexternalstoragedirectory().getabsolutepath() + "/reverseme.pcm"); log.i(tag,"生成文件"); //如果存在,就先删除再创建 if (file.exists()) file.delete(); log.i(tag,"删除文件"); try { file.createnewfile(); log.i(tag,"创建文件"); } catch (ioexception e) { log.i(tag,"未能创建"); throw new illegalstateexception("未能创建" + file.tostring()); } try { //输出流 outputstream os = new fileoutputstream(file); bufferedoutputstream bos = new bufferedoutputstream(os); dataoutputstream dos = new dataoutputstream(bos); int buffersize = audiorecord.getminbuffersize(frequency, channelconfiguration, audioencoding); audiorecord audiorecord = new audiorecord(mediarecorder.audiosource.mic, frequency, channelconfiguration, audioencoding, buffersize); short[] buffer = new short[buffersize]; audiorecord.startrecording(); log.i(tag, "开始录音"); isrecording = true; while (isrecording) { int bufferreadresult = audiorecord.read(buffer, 0, buffersize); for (int i = 0; i < bufferreadresult; i++) { dos.writeshort(buffer[i]); } } audiorecord.stop(); dos.close(); } catch (throwable t) { log.e(tag, "录音失败"); } } //播放文件 public void playrecord() { if(file == null){ return; } //读取文件 int musiclength = (int) (file.length() / 2); short[] music = new short[musiclength]; try { inputstream is = new fileinputstream(file); bufferedinputstream bis = new bufferedinputstream(is); datainputstream dis = new datainputstream(bis); int i = 0; while (dis.available() > 0) { music[i] = dis.readshort(); i++; } dis.close(); audiotrack audiotrack = new audiotrack(audiomanager.stream_music, 16000, audioformat.channel_configuration_mono, audioformat.encoding_pcm_16bit, musiclength * 2, audiotrack.mode_stream); audiotrack.play(); audiotrack.write(music, 0, musiclength); audiotrack.stop(); } catch (throwable t) { log.e(tag, "播放失败"); } } //删除文件 private void delefile() { if(file == null){ return; } file.delete(); printlog("文件删除成功"); } }
如果你想去调试这些pcm文件做音频测试的话,我推荐使用audacity这个软件,可以看到,我直接点击左上角的file-导入-源文件,然后设置16k
这样就可以调试了
最后,放一张完整的截图
以上所述是小编给大家介绍的android音频处理之通过audiorecord去保存pcm文件进行录制,播放,停止,删除功能,希望对大家有所帮助