Android录制声音文件(音频)并播放
程序员文章站
2024-02-28 15:36:10
本文实例为大家分享了android录制音频文件的具体代码,供大家参考,具体内容如下
1、这个demo中没有对多次点击同一个声音文件做详细处理,偶尔会有崩溃,用的时候需...
本文实例为大家分享了android录制音频文件的具体代码,供大家参考,具体内容如下
1、这个demo中没有对多次点击同一个声音文件做详细处理,偶尔会有崩溃,用的时候需要注意。
2、按住录音按钮录音过程中,只对竖直方向处理了一下,水平方向没写;
3、没有做删除某个声音文件的操作,但是测试的时候实现了功能,需要用到的话,在mainactivity—>onitemclick中的todo中有详细说明;
4、这只是个demo,如果要在项目中使用,先写出demo,没问题了,再引入项目,在写成demo后,在真机上运行的时候,如果出现获取录音权限,最好选择“允许”,如果拒绝,可能会崩溃。
记得打开手机运行录音的权限
先来效果图:
目录结构:
1、添加权限:
<uses-permission android:name="android.permission.record_audio"/> <uses-permission android:name="android.permission.wake_lock"/> <uses-permission android:name="android.permission.modify_audio_settings"/> <uses-permission android:name="android.permission.vibrate"/> <uses-permission android:name="android.permission.write_settings"/> <uses-permission android:name="android.permission.interact_across_users_full"/> <uses-permission android:name="android.permission.receive_boot_completed"/> <uses-permission android:name="android.permission.access_fine_location"/> <uses-permission android:name="android.permission.call_phone"/> <uses-permission android:name="android.permission.read_external_storage"/> <uses-permission android:name="android.permission.internet" /> <uses-permission android:name="android.permission.write_external_storage" /> <uses-permission android:name="android.permission.mount_unmount_filesystems" />
2、新建mediarecorderutils,复制以下源码:
package com.chen.voicedemo; import android.media.mediarecorder; import android.os.handler; import android.util.log; import android.widget.imageview; import java.io.file; /** * 录音工具类 */ public class mediarecorderutils { private static mediarecorder recorder; static mediarecorderutils mediarecorderutils; static imageview mimageview; private string path; /** * 获得单例对象,传入一个显示音量大小的imageview对象,如不需要显示可以传null */ public static mediarecorderutils getinstence(imageview imageview) { if (mediarecorderutils == null) { mediarecorderutils = new mediarecorderutils(); } mimageview = imageview; return mediarecorderutils; } /** * 获得音频路径 */ public string getpath() { return path; } /** * 初始化 */ private void init() { recorder = new mediarecorder();// new出mediarecorder对象 recorder.setaudiosource(mediarecorder.audiosource.mic); // 设置mediarecorder的音频源为麦克风 recorder.setoutputformat(mediarecorder.outputformat.raw_amr); // 设置mediarecorder录制的音频格式 recorder.setaudioencoder(mediarecorder.audioencoder.amr_nb); // 设置mediarecorder录制音频的编码为amr. file file = new file(utils.image_sdcard_mader); if (!file.exists()) { file.mkdirs(); } path = utils.image_sdcard_mader + utils.getvoicefilename() + "stock.amr"; recorder.setoutputfile(path); // 设置录制好的音频文件保存路径 try { recorder.prepare();// 准备录制 } catch (exception e) { e.printstacktrace(); } } /** * 开始录音 */ public void mediarecorderstart() { init(); try { recorder.start(); flag = true; if (mimageview != null) { updatemicstatus(); } } catch (exception e) { e.printstacktrace(); log.e("chen", "录制失败"); } } /** * 停止录音 */ public void mediarecorderstop() { try { recorder.stop(); recorder.release(); //释放资源 flag = false; mimageview = null; recorder = null; } catch (exception e) { e.tostring(); } } /** * 删除已录制的音频 */ public void mediarecorderdelete() { file file = new file(path); if (file.isfile()) { file.delete(); } file.exists(); } ; private final handler mhandler = new handler(); private runnable mupdatemicstatustimer = new runnable() { public void run() { updatemicstatus(); } }; private int base = 1; private int space = 1000;// 间隔取样时间 private boolean flag = true; /** * 更新话筒状态 */ private void updatemicstatus() { if (recorder != null) { double ratio = (double) recorder.getmaxamplitude() / base; double db = 0;// 分贝 if (ratio > 1) { db = 20 * math.log10(ratio); } int i = (int) db / 10; switch (i) { case 1: mimageview.setimageresource(r.drawable.rc_ic_volume_1); break; case 2: mimageview.setimageresource(r.drawable.rc_ic_volume_2); break; case 3: mimageview.setimageresource(r.drawable.rc_ic_volume_3); break; case 4: mimageview.setimageresource(r.drawable.rc_ic_volume_4); break; case 5: mimageview.setimageresource(r.drawable.rc_ic_volume_5); break; case 6: mimageview.setimageresource(r.drawable.rc_ic_volume_6); break; case 7: mimageview.setimageresource(r.drawable.rc_ic_volume_7); break; case 8: mimageview.setimageresource(r.drawable.rc_ic_volume_8); break; } if (flag) { mhandler.postdelayed(mupdatemicstatustimer, space); } } } }
3、创建mychronometer,复制以下代码
package com.chen.voicedemo; import android.annotation.suppresslint; import android.content.context; import android.os.handler; import android.os.message; import android.os.systemclock; import android.util.attributeset; import android.view.accessibility.accessibilityevent; import android.view.accessibility.accessibilitynodeinfo; import android.widget.textview; public class mychronometer extends textview { private static final string tag = "mychronometer"; /** * a callback that notifies when the mychronometer has incremented on its * own. */ public interface onmychronometerticklistener { /** * notification that the mychronometer has changed. */ void onmychronometertick(int time); } public interface onmychronometertimelistener { /** * notification that the mychronometer has changed. */ void onmychronometertimelistener(int time); } private onmychronometertimelistener onmychronometertimelistener; private long mbase; private boolean mvisible; private boolean mstarted; private boolean mrunning; private onmychronometerticklistener monmychronometerticklistener; private long now_time; private static final int tick_what = 2; /** * initialize this mychronometer object. sets the base to the current time. */ public mychronometer(context context) { this(context, null, 0); } /** * initialize with standard view layout information. sets the base to the * current time. */ public mychronometer(context context, attributeset attrs) { this(context, attrs, 0); } /** * initialize with standard view layout information and style. sets the base * to the current time. */ public mychronometer(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); init(); } private void init() { mbase = systemclock.elapsedrealtime(); updatetext(mbase); } /** * set the time that the count-up timer is in reference to. * * @param base use the {@link systemclock#elapsedrealtime} time base. */ public void setbase(long base) { mbase = base; updatetext(systemclock.elapsedrealtime()); } /** * sets the listener to be called when the mychronometer changes. * * @param listener the listener. */ public void setonmychronometerticklistener(onmychronometerticklistener listener) { monmychronometerticklistener = listener; } public void setonmychronometertimelistener(onmychronometertimelistener listener) { onmychronometertimelistener = listener; } /** * start counting up. this does not affect the base as set from * {@link #setbase}, just the view display. * <p/> * mychronometer works by regularly scheduling messages to the handler, even * when the widget is not visible. to make sure resource leaks do not occur, * the user should make sure that each start() call has a reciprocal call to * {@link #stop}. */ public void start() { mstarted = true; updaterunning(); } /** * stop counting up. this does not affect the base as set from * {@link #setbase}, just the view display. * <p/> * this stops the messages to the handler, effectively releasing resources * that would be held as the mychronometer is running, via {@link #start}. */ public void stop() { mstarted = false; updaterunning(); now_time /= 10; if (onmychronometertimelistener != null) { onmychronometertimelistener.onmychronometertimelistener((int) now_time); } } @override protected void ondetachedfromwindow() { super.ondetachedfromwindow(); mvisible = false; updaterunning(); } @override protected void onwindowvisibilitychanged(int visibility) { super.onwindowvisibilitychanged(visibility); mvisible = visibility == visible; updaterunning(); } private synchronized void updatetext(long now) { long seconds = now - mbase; seconds /= 10; now_time = seconds; int time_m = (int) (seconds / 100); if (monmychronometerticklistener != null) { monmychronometerticklistener.onmychronometertick(time_m); } int time_s = (int) (seconds % 100); settext(time_m + ""); } private void updaterunning() { boolean running = mvisible && mstarted; if (running != mrunning) { if (running) { updatetext(systemclock.elapsedrealtime()); mhandler.sendmessagedelayed(message.obtain(mhandler, tick_what), 1000); } else { mhandler.removemessages(tick_what); } mrunning = running; } } private handler mhandler = new handler() { public void handlemessage(message m) { if (mrunning) { updatetext(systemclock.elapsedrealtime()); sendmessagedelayed(message.obtain(this, tick_what), 1000); } } }; @suppresslint("newapi") @override public void oninitializeaccessibilityevent(accessibilityevent event) { super.oninitializeaccessibilityevent(event); event.setclassname(mychronometer.class.getname()); } @suppresslint("newapi") @override public void oninitializeaccessibilitynodeinfo(accessibilitynodeinfo info) { super.oninitializeaccessibilitynodeinfo(info); info.setclassname(mychronometer.class.getname()); } }
4、创建工具类
package com.chen.voicedemo;
import android.manifest; import android.content.context; import android.content.pm.packagemanager; import android.os.environment; import android.support.v4.content.contextcompat; import android.widget.toast; import java.io.file; import java.text.simpledateformat; import java.util.arraylist; /** * 工具 */ public class utils { /** * sd卡下语音目录 */ public static final string image_sdcard_mader = environment .getexternalstoragedirectory() + "/chen/voice/"; /** * 检查录音权限6.0 */ public static boolean checkvoice(context context) { try { if (contextcompat.checkselfpermission(context, manifest.permission.record_audio) != packagemanager.permission_granted) { return false; } else { return true; } } catch (exception e) { return true; } } private static toast toast; /** * 单例吐司 */ public static void showtoast(context context, string msg) { if (toast == null) { toast = toast.maketext(context, msg, toast.length_short); } toast.settext(msg); toast.show(); } /** * 获取指定文件夹下的所有文件路径 * * @param root 指定文件夹路径 * @return 指定文件夹下的所有文件 */ public static arraylist<string> getvideofiles(string root) { if (root == null || root == "") return null; arraylist<string> list = new arraylist<>(); file file = new file(root); file[] filelist = file.listfiles(); for (file f : filelist) { list.add(f.getpath()); } return list; } /** * 获取声音文件名字 * * @return 假如当前录制声音时间是2016年4月29号14点30分30秒。得到的文件名字就是20160429143030.这样保证文件名的唯一性 */ public static string getvoicefilename() { long getnowtimelong = system.currenttimemillis(); simpledateformat time = new simpledateformat("yyyymmddhhmmss"); string result = time.format(getnowtimelong); return result; } }
5、mainactivity
package com.chen.voicedemo; import android.app.activity; import android.graphics.drawable.animationdrawable; import android.graphics.drawable.colordrawable; import android.media.mediaplayer; import android.os.bundle; import android.os.systemclock; import android.util.log; import android.view.layoutinflater; import android.view.motionevent; import android.view.view; import android.view.viewgroup; import android.view.window; import android.view.windowmanager; import android.widget.adapterview; import android.widget.baseadapter; import android.widget.imageview; import android.widget.listview; import android.widget.popupwindow; import android.widget.textview; import java.io.file; import java.util.arraylist; import java.util.list; public class mainactivity extends activity implements view.ontouchlistener, adapterview.onitemclicklistener { /** * 开始录音按钮 */ private textview voice; /** * 用于定位。使录音时展示的popupwindow,展示在该控件 的下面 */ private textview voice_popup; /** * 展示指定文件夹下所有录制的声音文件 */ private textview show_voice_list; /** * 展示目标文件夹下,所有已录制的声音路径 */ private listview show_voices_listview; private list<string> voicelist; /** * 停止播放声音 */ private textview stop_show_voice; /** * 播放声音时,动的图片 */ private imageview voice_anim; /** * 系统播放器 */ private mediaplayer mediaplayer; private boolean flag = true; private float int_x = 0; private float int_y = 0; /** * 用于限制最大录音时常。单位是秒。意义是:最大录60秒的音频,到了60秒的是,自动停止 */ private int maxrecordtime = 60; /** * 用于显示频繁操作时间间隔。单位是毫秒。意义是:500毫秒内再次操作,就算是频频操作,做相应处理 */ private int oftenoperationtime = 500; private myadapter myadapter; private animationdrawable animation; /** * 录音popup */ private popupwindow voice_popupwindow; /** * 录音时声音变化 */ private imageview voice_shengyin; /** * 录音计时器 */ private mychronometer mychronometer; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); requestwindowfeature(window.feature_no_title); setcontentview(r.layout.activity_main); voicelist = new arraylist<string>(); voice = (textview) findviewbyid(r.id.voice); voice_popup = (textview) findviewbyid(r.id.voice_popup); voice_anim = (imageview) findviewbyid(r.id.voice_anim); voice_anim.setimageresource(r.drawable.lcs_voice_receive); show_voice_list = (textview) findviewbyid(r.id.show_voice_list); show_voice_list.setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) { voicelist = utils.getvideofiles(utils.image_sdcard_mader); if (voicelist.size()>0){ myadapter.notifydatasetchanged(); }else{ utils.showtoast(mainactivity.this, "没有文件"); } } }); show_voices_listview = (listview) findviewbyid(r.id.show_voices); show_voices_listview.setonitemclicklistener(this); myadapter = new myadapter(); stop_show_voice = (textview) findviewbyid(r.id.stop_show_voice); /** * 停止播放的监听器 */ stop_show_voice.setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) { log.e("chen", "点击了停止播放按钮"); if (mediaplayer != null) { if (mediaplayer.isplaying()) { mediaplayer.release();// 释放资源 } mediaplayer = null; } if (animation != null && animation.isrunning()) { animation.stop(); } voice_anim.setimageresource(r.drawable.lcs_voice_receive); } }); show_voices_listview.setadapter(myadapter); voice.setontouchlistener(this); } /** * 声音文件列表的item点击事件,播放对应声音文件 * * @param parent * @param view * @param position * @param id */ @override public void onitemclick(adapterview<?> parent, view view, int position, long id) { //todo 以下4行,是用来做测试,点击item,手机sd卡上对应路径下的声音文件就会被删除。如果录制声音失败,或者不满足条件,可以把以下4行写成一个工具方法调用,删除不满意的文件。这里不做详细演示 //file f_delete=new file(voicelist.get(position)); //f_delete.delete(); //voicelist.remove(voicelist.get(position)); //myadapter.notifydatasetchanged(); //todo 以上4行,是用来做测试,点击item,手机sd卡上对应路径下的声音文件就会被删除。 try { mediaplayer = new mediaplayer(); /** * 播放过程中展示的动画 */ mediaplayer.setonpreparedlistener(new mediaplayer.onpreparedlistener() { @override public void onprepared(mediaplayer mp) { if (mp != null) { mp.start(); voice_anim.setimageresource(r.drawable.voice_anim); } } }); /** * 播放完成监听 */ mediaplayer.setoncompletionlistener(new mediaplayer.oncompletionlistener() { @override public void oncompletion(mediaplayer mp) { if (mp.isplaying()) { mp.release();// 释放资源 } animation = (animationdrawable) voice_anim.getdrawable(); if (animation != null && animation.isrunning()) { animation.stop(); } voice_anim.setimageresource(r.drawable.lcs_voice_receive); } }); mediaplayer.setdatasource(voicelist.get(position)); // 缓冲 mediaplayer.prepare(); } catch (exception e) { utils.showtoast(mainactivity.this, "语音异常,加载失败"); } } /** * 展示声音列表的adapter */ class myadapter extends baseadapter { @override public int getcount() { return voicelist.size() == 0 ? 0 : voicelist.size(); } @override public object getitem(int position) { return null; } @override public long getitemid(int position) { return 0; } @override public view getview(int position, view convertview, viewgroup parent) { textview tv = new textview(mainactivity.this); tv.settext(voicelist.get(position)); tv.settextsize(20); return tv; } } /** * 开始录制按钮的ontouch事件 * * @param v * @param event * @return */ @override public boolean ontouch(view v, motionevent event) { if (v.getid() == r.id.voice) { //检查权限 if (!utils.checkvoice(this)) { if (event.getaction() == motionevent.action_down) { utils.showtoast(this, "录音权限未打开,请打开录音权限!"); } return true; } //避免短时间里频繁操作 if (!gettimetf(systemclock.elapsedrealtime()) && event.getaction() == motionevent.action_down) { utils.showtoast(this, "操作过于频繁"); return true; } if (event.getaction() == motionevent.action_down) { settime(systemclock.elapsedrealtime()); } switch (event.getaction()) { case motionevent.action_down: int_x = event.getrawx(); int_y = event.getrawy(); voicepopupwindow(); mychronometer.setbase(systemclock.elapsedrealtime()); mychronometer.start(); mediarecorderutils.getinstence(voice_shengyin).mediarecorderstart(); flag = true; mychronometer.setonmychronometerticklistener(new mychronometer.onmychronometerticklistener() { @override public void onmychronometertick(int time) { if (time == maxrecordtime || time > maxrecordtime) { mychronometer.settext("60"); setvoicetoup(); } } }); break; case motionevent.action_move: if (flag) { if (math.abs(int_y) - math.abs(event.getrawy()) > 100.0 && flag) { voice_popupwindow.dismiss(); mychronometer.stop(); mediarecorderutils.getinstence(voice_shengyin).mediarecorderstop(); mediarecorderutils.getinstence(voice_shengyin).mediarecorderdelete(); flag = false; } } break; case motionevent.action_cancel: if (flag) { voice_popupwindow.dismiss(); mychronometer.stop(); mediarecorderutils.getinstence(voice_shengyin).mediarecorderstop(); } break; case motionevent.action_up: if (flag) { setvoicetoup(); } break; } return true; } return false; } private long base_time = 0; private void settime(long time) { base_time = time; } private boolean gettimetf(long time) { int data = (int) (time - base_time) / oftenoperationtime; if (data > 1) { return true; } else { return false; } } /** * 声音popupwindow */ public void voicepopupwindow() { view view = layoutinflater.from(this).inflate(r.layout.voice_popupwindow, null); voice_popupwindow = new popupwindow(this); voice_popupwindow.setwidth(windowmanager.layoutparams.match_parent); voice_popupwindow.setheight(windowmanager.layoutparams.match_parent); voice_shengyin = (imageview) view.findviewbyid(r.id.voice_shengyin); mychronometer = (mychronometer) view.findviewbyid(r.id.mychronometer); voice_popupwindow.setcontentview(view); voice_popupwindow.setfocusable(true); colordrawable dw = new colordrawable(0x00000000); voice_popupwindow.setbackgrounddrawable(dw); voice_popupwindow.showasdropdown(voice_popup); } private void setvoicetoup() { flag = false; voice_popupwindow.dismiss(); mychronometer.stop(); mediarecorderutils.getinstence(voice_shengyin).mediarecorderstop(); int time = integer.parseint(mychronometer.gettext().tostring()); if (time != 0) { file file = new file(mediarecorderutils.getinstence(voice_shengyin).getpath()); if (file.length() > 0) { voicelist = utils.getvideofiles(utils.image_sdcard_mader); myadapter.notifydatasetchanged(); } else { utils.showtoast(this, "录音失败,请检查权限"); } } else { utils.showtoast(this, "录音时间太短"); } } }
6、activity_main布局
<?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" > <textview android:id="@+id/voice_popup" android:layout_width="match_parent" android:layout_height="1dip"/> <listview android:id="@+id/show_voices" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <relativelayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <imageview android:id="@+id/voice_anim" android:layout_width="60dp" android:layout_height="30dp" android:layout_centervertical="true" android:layout_marginleft="30dp" android:background="#00ff00"/> <textview android:id="@+id/stop_show_voice" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignparentright="true" android:layout_centervertical="true" android:layout_gravity="center_horizontal" android:layout_marginbottom="20dp" android:layout_marginright="20dp" android:background="#00ff00" android:padding="10dp" android:text="停止播放" android:textcolor="#000000" android:textsize="20sp" /> <textview android:id="@+id/show_voice_list" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centervertical="true" android:layout_gravity="center_horizontal" android:layout_marginbottom="20dp" android:layout_marginright="20dp" android:layout_toleftof="@id/stop_show_voice" android:background="#00ff00" android:padding="10dp" android:text="列表" android:textcolor="#000000" android:textsize="20sp" /> </relativelayout> <textview android:id="@+id/voice" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:background="#00ff00" android:padding="10dp" android:text="开始录音" android:textcolor="#000000" android:textsize="25sp"/> </linearlayout>
7、voice_popupwindow布局代码:录音的时候,会出现以下图片中的popupwindow
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <linearlayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerinparent="true" android:background="@android:color/black" android:orientation="vertical" android:paddingbottom="40dip" android:paddingleft="60dip" android:paddingright="60dip" android:paddingtop="40dip"> <imageview android:id="@+id/voice_shengyin" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:src="@drawable/rc_ic_volume_1"/> <com.chen.voicedemo.mychronometer android:id="@+id/mychronometer" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerhorizontal="true" android:layout_gravity="center_horizontal" android:textcolor="@android:color/white"/> </linearlayout> </relativelayout>
8、还有一个动画布局,播放声音的时候,有个动画效果
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/volume_animation" android:oneshot="false" > <item android:drawable="@drawable/rc_ic_voice_receive_play1" android:duration="100"/> <item android:drawable="@drawable/rc_ic_voice_receive_play2" android:duration="200"/> <item android:drawable="@drawable/rc_ic_voice_receive_play3" android:duration="300"/> </animation-list>
附录:用到的图片资源说明:如果手上没有这样的图片,可以随便用其他图片代替,有效果,就算成功
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: 详解SpringBoot配置连接池
推荐阅读