Android实现长按录音松开保存、播放及根据声贝动画展示
Android实现长按录音松开保存及根据声贝动画展示
- 1、准备两张需要动态展示的图片
- 2、布局文件popup_window.xml
- 3、popup.xml 文件
- 4、封装MediaRecorder初始化及相关操作AudioRecoderUtils
- 5、Activity代码实现
- 6、录音及播放权限
- 7、总结
- 8、效果图
前言:做一个有梦想的程序猿! |
---|
最近公司需要本人负责开发一款app,其中有个实现录音、播放等功能,作为以java后台开发为主的我,以前没怎么接触过安卓开发,也罢,为了努力提升自身技术水平实现人生理想,只能咬牙接下重任。
在做这个功能之前,在网上也查阅了些资料,实现过程中确实遇到了一些坑,所以在此记录下来分享给各位,废话不多说,直接步入正题:
Android提供了两个API用于实现录音功能:android.media.AudioRecord、android.media.MediaRecorder,这两个各有各的优缺点 :
简单来说就是AudioRecord主要实现边录边播,也可以实时的进行音频处理,而它的缺点是输出的是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩.
而MediaRecorder优点是大部分以及集成,直接调用相关接口即可,代码量小,缺点是无法实时处理音频,输出的音频格式不是很多,例如没有输出mp3格式文件,当然AudioRecord是可以实现输出mp3格式的,不过需要工具转码,这里就不多说了。
接下来主要介绍MediaRecorder的使用方法:
1、准备两张需要动态展示的图片
2、布局文件popup_window.xml
<?xml version="1.0" encoding="utf-8"?>
popup_window.xml便是动画展示的界面
3、popup.xml 文件
<?xml version="1.0" encoding="utf-8"?> -
popup.xml文件根据声贝大小动态剪切recoding.png到normal.png上,这样就实现了动画音频效果
4、封装MediaRecorder初始化及相关操作AudioRecoderUtils
package com.gtlxkj.cn.util;import java.io.File;import java.io.IOException;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import com.gtlxkj.cn.activity.FaultActivity;import android.content.Context;import android.media.MediaPlayer;import android.media.MediaRecorder;import android.os.Handler;import android.util.Log;public class AudioRecoderUtils extends FaultActivity{ //文件路径 private String filePath; //文件夹路径 private String FolderPath; private MediaRecorder mMediaRecorder; private MediaPlayer player; private final String TAG = "fan"; public static final int MAX_LENGTH = 1000 * 60 * 10;// 最大录音时长1000*60*10; private OnAudioStatusUpdateListener audioStatusUpdateListener; /** * 文件存储默认sdcard/record */ public AudioRecoderUtils(){ //默认保存路径为/sdcard/record/下 this(ConfigurationUtil.RECORD_PATH_ABSOULT); } public AudioRecoderUtils(String filePath) { File path = new File(filePath); if(!path.exists()) path.mkdirs(); this.FolderPath = filePath; } private long startTime; private long endTime; /** * 开始录音 使用mp4格式 * 录音文件 * @return */ public void startRecord() { // 开始录音 /* ①Initial:实例化MediaRecorder对象 */ if (mMediaRecorder == null) mMediaRecorder = new MediaRecorder(); try { /* ②setAudioSource/setVedioSource */ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风 /* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */ mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); /* * ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式 * ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB) */ mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); filePath = FolderPath + System.currentTimeMillis() + ".mp4" ; /* ③准备 */ mMediaRecorder.setOutputFile(filePath); mMediaRecorder.setMaxDuration(MAX_LENGTH); mMediaRecorder.prepare(); /* ④开始 */ mMediaRecorder.start(); // AudioRecord audioRecord. /* 获取开始时间* */ startTime = System.currentTimeMillis(); updateMicStatus(); Log.i("fan", "startTime" + startTime); } catch (IllegalStateException e) { Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage()); } catch (IOException e) { Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage()); } } /** * 停止录音 */ public long stopRecord() { if (mMediaRecorder == null) return 0L; endTime = System.currentTimeMillis(); //有一些网友反应在5.0以上在调用stop的时候会报错,翻阅了一下谷歌文档发现上面确实写的有可能会报错的情况,捕获异常清理一下就行了,感谢大家反馈! try { mMediaRecorder.stop(); mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; audioStatusUpdateListener.onStop(filePath); filePath = ""; }catch (RuntimeException e){ mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; File file = new File(filePath); if (file.exists()) file.delete(); filePath = ""; } return endTime - startTime; } /** * 播放录音 * @param filepath */ public MediaPlayer playRecord(String filepath) { player = getMediaPlayer(this);if(player != null){player.reset();try {//设置语言的来源player.setDataSource(filepath);//初始化player.prepare();//开始播放player.start();}catch (IOException e) {e.printStackTrace();}}return player;} /** * 取消录音 */ public void cancelRecord(){ try { mMediaRecorder.stop(); mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; }catch (RuntimeException e){ mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; } File file = new File(filePath); if (file.exists()) file.delete(); filePath = ""; } private final Handler mHandler = new Handler(); private Runnable mUpdateMicStatusTimer = new Runnable() { public void run() { updateMicStatus(); } }; private int BASE = 1; private int SPACE = 100;// 间隔取样时间 public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) { this.audioStatusUpdateListener = audioStatusUpdateListener; } /** * 更新麦克状态 */ private void updateMicStatus() { if (mMediaRecorder != null) { double ratio = (double)mMediaRecorder.getMaxAmplitude() / BASE; double db = 0;// 分贝 if (ratio > 1) { db = 20 * Math.log10(ratio); if(null != audioStatusUpdateListener) { audioStatusUpdateListener.onUpdate(db,System.currentTimeMillis()-startTime); } } mHandler.postDelayed(mUpdateMicStatusTimer, SPACE); } } public interface OnAudioStatusUpdateListener { /** * 录音中... * @param db 当前声音分贝 * @param time 录音时长 */ public void onUpdate(double db,long time); /** * 停止录音 * @param filePath 保存路径 */ public void onStop(String filePath); } /** * This code is trying to do the following from the hidden API * SubtitleController sc = new SubtitleController(context, null, null); * sc.mHandler = new Handler(); * mediaplayer.setSubtitleAnchor(sc, null) */ private MediaPlayer getMediaPlayer(Context context) { MediaPlayer mediaplayer = new MediaPlayer(); if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { return mediaplayer; } try { Class<?> cMediaTimeProvider = Class.forName("android.media.MediaTimeProvider"); Class<?> cSubtitleController = Class.forName("android.media.SubtitleController"); Class<?> iSubtitleControllerAnchor = Class.forName("android.media.SubtitleController$Anchor"); Class<?> iSubtitleControllerListener = Class.forName("android.media.SubtitleController$Listener"); Constructor constructor = cSubtitleController.getConstructor( new Class[]{Context.class, cMediaTimeProvider, iSubtitleControllerListener}); Object subtitleInstance = constructor.newInstance(context, null, null); Field f = cSubtitleController.getDeclaredField("mHandler"); f.setAccessible(true); try { f.set(subtitleInstance, new Handler()); } catch (IllegalAccessException e) { return mediaplayer; } finally { f.setAccessible(false); } Method setsubtitleanchor = mediaplayer.getClass().getMethod("setSubtitleAnchor", cSubtitleController, iSubtitleControllerAnchor); setsubtitleanchor.invoke(mediaplayer, subtitleInstance, null); } catch (Exception e) { Log.d(TAG,"getMediaPlayer crash ,exception = "+e); } return mediaplayer; } }
相关工具类:
package com.gtlxkj.cn.util;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.Date;import android.util.Log;public class TimeUtils {/** * 获取当前时间 * @return */ public static String getNowTime(){ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); Date date = new Date(System.currentTimeMillis()); return simpleDateFormat.format(date); } /** * 获取时间戳 * * @return 获取时间戳 */ public static String getTimeString() { SimpleDateFormat df = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); Calendar calendar = Calendar.getInstance(); return df.format(calendar.getTime()); } /** * 时间转换为时间戳 * @param time:需要转换的时间 * @return */ public static String dateToStamp(String time) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date = null; try { date = simpleDateFormat.parse(time); } catch (Exception e) { e.printStackTrace(); } long ts = date.getTime(); return String.valueOf(ts); } /** * 时间戳转换为字符串 * @param time:时间戳 * @return */ public static String getDateToString(long time) { Date d = new Date(time); SimpleDateFormat sf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); return sf.format(d); } /** * 时间戳转换为字符串分秒 * @param time:时间戳 * @return */ public static String getDateCoverString(long time) { Date d = new Date(time); SimpleDateFormat sf = new SimpleDateFormat("mm:ss"); return sf.format(d); } /** *获取距现在某一小时的时刻 * @param hour hour=-1为上一个小时,hour=1为下一个小时 * @return */ public static String getLongTime(int hour){ Calendar c = Calendar.getInstance(); // 当时的日期和时间 int h; // 需要更改的小时 h = c.get(Calendar.HOUR_OF_DAY) - hour; c.set(Calendar.HOUR_OF_DAY, h); SimpleDateFormat df = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); Log.v("time",df.format(c.getTime())); return df.format(c.getTime()); } }
5、Activity代码实现
@SuppressLint("NewApi")@ContentView(R.layout.vw_head_fault)public class FaultActivity extends BaseActivity{ private View view ; @ViewInject(R.id.buttonPressToSpeak) private Button mButton; @ViewInject(R.id.buttonBroadcast) private Button cButton; @ViewInject(R.id.head_broadcast) private TextView head_broadcast; @ViewInject(R.id.ivAnim) private ImageView ivAnim; private AnimationDrawable drawable; private ImageView micImage; private TextView recordingTime; private long ltime; private MediaPlayer player; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.context=FaultActivity.this; Log.i("XListViewActivity", "onCreate"); initVedio(); } //录音功能 初始化 private void initVedio(){ view = View.inflate(this, R.layout.popup_window, null); //设置空白的背景色 final WindowManager.LayoutParams lp = FaultActivity.this.getWindow().getAttributes(); final PopupWindow mPop = new PopupWindow(view); micImage=(ImageView)view.findViewById(R.id.iv_pro); recordingTime=(TextView)view.findViewById(R.id.recording_time); mAudioRecoderUtils = new AudioRecoderUtils(); //录音回调 mAudioRecoderUtils.setOnAudioStatusUpdateListener(new AudioRecoderUtils.OnAudioStatusUpdateListener() { //录音中....db为声音分贝,time为录音时长 @Override public void onUpdate(double db, long time) { //根据分贝值来设置录音时话筒图标的上下波动 ltime=time; micImage.getDrawable().setLevel((int) (3000 + 6000 * db / 100)); recordingTime.setText(TimeUtils.getDateCoverString(time)); } //录音结束,filePath为保存路径 @Override public void onStop(String filePath) { if(ltime<1500){//判断,如果录音时间小于1.5秒,则删除文件提示,过短 File file = new File(filePath); if(file.exists()){//判断文件是否存在,如果存在删除文件 file.delete();//删除文件 Toast.makeText(FaultActivity.this, "录音时间过短",Toast.LENGTH_SHORT).show(); } }else{ try { //保存录音路径前先删除旧的文件 if(!Utils.StringEx(fault.getSoundpath())){ File file = new File(fault.getSoundpath()); if(file.exists()){//判断文件是否存在,如果存在删除文件 file.delete();//删除文件 } } fault.setSoundpath(filePath); getDBManager().saveOrUpdate(fault); Toast.makeText(FaultActivity.this, "录音保存在:" + filePath, Toast.LENGTH_SHORT).show(); recordingTime.setText("00:00"); ltime=0;} catch (DbException e) {e.printStackTrace();} } } }); //Button的touch监听 mButton.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: lp.alpha = 0.4f; FaultActivity.this.getWindow().setAttributes(lp); mPop.setWidth(500); mPop.setHeight(500); mPop.showAtLocation(rl,Gravity.CENTER,0,0); mAudioRecoderUtils.startRecord(); break; case MotionEvent.ACTION_UP://恢复背景色 lp.alpha = 1f; FaultActivity.this.getWindow().setAttributes(lp); mAudioRecoderUtils.stopRecord(); //结束录音(保存录音文件) mPop.dismiss(); break; } return true; } }); //播放录音 cButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if(Utils.StringEx(fault.getSoundpath())){Toast.makeText(FaultActivity.this, "此故障信息暂无录音内容", Toast.LENGTH_SHORT).show();}else{if(player==null){player = mAudioRecoderUtils.playRecord(fault.getSoundpath());//播放 cButton.setBackgroundResource(R.drawable.zanting); head_broadcast.setText("正在播放"); ivAnim.setBackgroundResource(R.drawable.anim); drawable = (AnimationDrawable)ivAnim.getBackground(); drawable.start(); player.setOnCompletionListener(new OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) {//监听是否播放完毕 cButton.setBackgroundResource(R.drawable.play); head_broadcast.setText("点击播放"); player.release();//释放资源 player=null; drawable.stop(); ivAnim.setBackgroundResource(R.drawable.bofang3); } });}else if(player.isPlaying()){ player.stop(); player.release();//释放资源 player=null; cButton.setBackgroundResource(R.drawable.play); head_broadcast.setText("点击播放"); drawable.stop(); ivAnim.setBackgroundResource(R.drawable.bofang3);}}}}); }}
此Activity同样实现了播放喇叭动画展示以及录音中背景变暗等效果
6、录音及播放权限
最后就是加权限了,在AndroidManifest.xml文件中加入下面两句
7、总结
其实安卓实现录音功能并不复杂,明白了其中的调用流程就会感觉简单许多,根据声贝展动画界面需要在代码中指定PopupWindow的大小,否则可能会出现无法展示问题,后期也实现了批量上传录音及图片到服务器功能,而用这种方法上传的录音经过测试有wav和mp4格式录音文件上传到服务器可以播放,mp3格式会提示播放失败,其他格式暂时还没测试。
以上叙述有误的地方还请各位及时提出改正
8、效果图
界面没重新设计就没那么美观了
录音:
播放:
最后,如果本篇文章对您有所帮助,可以评论或点赞支持一下哦,感谢感谢!
更多相关文章
- 一款常用的 Squid 日志分析工具
- GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
- RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
- Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
- android电源管理资料整理
- My Magic Android(安卓)Tour —— 处女作
- Android(安卓)万能适配器 节省你的开发时间
- Android文件上传下载
- Android设备的内置存储和外置存储到底是怎么回事,深入理解