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"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/popup"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_alignParentBottom="true"android:orientation="vertical"><ImageViewandroid:id="@+id/iv_pro"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:layout_marginTop="30dp"android:gravity="center"android:src="@drawable/popup" /><TextViewandroid:id="@+id/recording_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@+id/iv_pro"android:layout_centerHorizontal="true"android:layout_marginTop="10dp"android:textColor="#FFFFFF" android:padding="2dp"android:textSize="17sp" />  <TextViewandroid:id="@+id/recording_tip"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/recording_time"android:layout_centerHorizontal="true"android:layout_marginTop="10dp"android:textColor="#FFFFFF" android:text="正在录音,松开保存" android:padding="2dp"android:textSize="17sp" />
</RelativeLayout>

popup_window.xml便是动画展示的界面

3、popup.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" ><item android:id="@android:id/background" android:drawable="@drawable/normal"/><item android:id="@android:id/progress" ><clip android:drawable="@drawable/recoding" android:gravity="bottom" android:clipOrientation="vertical" /></item>
</layer-list>

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);}/***  </br> This code is trying to do the following from the hidden API* </br> SubtitleController sc = new SubtitleController(context, null, null);* </br> sc.mHandler = new Handler();* </br> mediaplayer.setSubtitleAnchor(sc, null)</p>*/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;@Overrideprotected 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为录音时长@Overridepublic void onUpdate(double db, long time) {//根据分贝值来设置录音时话筒图标的上下波动ltime=time;micImage.getDrawable().setLevel((int) (3000 + 6000 * db / 100));recordingTime.setText(TimeUtils.getDateCoverString(time));}//录音结束,filePath为保存路径@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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文件中加入下面两句

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

7、总结

其实安卓实现录音功能并不复杂,明白了其中的调用流程就会感觉简单许多,根据声贝展动画界面需要在代码中指定PopupWindow的大小,否则可能会出现无法展示问题,后期也实现了批量上传录音及图片到服务器功能,而用这种方法上传的录音经过测试有wav和mp4格式录音文件上传到服务器可以播放,mp3格式会提示播放失败,其他格式暂时还没测试。
以上叙述有误的地方还请各位及时提出改正

8、效果图

界面没重新设计就没那么美观了
录音:

播放:

最后,如果本篇文章对您有所帮助,可以评论或点赞支持一下哦,感谢感谢!

Android实现长按录音松开保存、播放及根据声贝动画展示相关推荐

  1. uniapp小程序实现录音 uniapp小程序长按录音 点击播放等功能(CSS实现语音音阶动画效果)

    最近项目使用uniapp开发微信小程序,需要实现一个长按时进行语音录制,限制录制时间最大为60秒,录制完成后,可点击播放,播放时再次点击停止播放,录制完成长按实现删除功能,删除后又可重新录制(如上图所 ...

  2. 71.android 简单的电话录音并保存到本地(来电和去电都支持)

    //第一步 先加权限 在AndroidManifest.xml里: //有打电话的权限,读写权限,还有录音权限. <uses-permission android:name="andr ...

  3. android怎么长按一张图片保存到相册_instagram怎么保存图片?

    instagram怎么保存图片方法一: 1.在iOS设备上,导航到个人资料标签. 2.点按右上角的齿轮图标即可访问设置. 注意:如果您使用的是Android设备,则可能需要点击屏幕右上角的三行菜单图标 ...

  4. android怎么长按一张图片保存到相册_好看的微信朋友圈背景图片下载 让你的朋友圈封面个性起来...

    最近回老家了,由于风景比较好,发朋友圈的次数相比以前更多了一些,朋友之间的互动也明显多了一些.最近有朋友留言说,你的朋友圈封面挺个性的,也想要. 其实,之前为小伙伴们带来过好几期朋友圈封面背景图片,而 ...

  5. Android 音视频开发(一) -- 使用AudioRecord 录制PCM(录音);AudioTrack播放音频

    前言,音视频这块,确实比较难入门,本着学习的态度,我这边也跟着 Android 音视频开发入门指南 打怪升级,留下个脚印,大家共勉. 音视频 系列文章 Android 音视频开发(一) – 使用Aud ...

  6. Android开发之PCM录音实时播放的实现方法 | 边录音边播放 |PCM录音播放无延迟 | 录音无杂音 | 录音无噪音

    先说下录音得开启录音权限 <uses-permission android:name="android.permission.RECORD_AUDIO" /> 然后录音 ...

  7. android进行录音功能并保存播放

    在android中进行录音相对来说是比较简单的,使用系统提供的MediaRecorder类进行录音并保存,然后调用MediaPlayer进行播放.以下为xml配置文件代码: <RelativeL ...

  8. 上传声音 微信小程序_微信小程序录音文件保存,播放

    最近做微信小程序需要把录音文件保存起来然后在另一个地方再播放,录音用的是wx.startRecord接口返回的是录音文件的临时路径,开始以为是和选择图片保存图片没什么两样的操作,结果和后端同事一起折腾 ...

  9. Android开发本地及网络Mp3音乐播放器(十)最近播放界面与数据保存更新

    实现功能: 实现MyLoveMusicActivity(音乐收藏界面) 实现MyRecordMusicActivity(最近播放界面) 实现MyMusicListFragment(本地音乐界面)Ite ...

最新文章

  1. 虚函数表剖析,网上转的,呵呵
  2. 运维老鸟谈生产场景如何对linux系统进行分区?
  3. 快捷键截屏_QQ的Ctrl+Alt+A快捷键除了截屏,竟然还有这么多好用的功能!
  4. spring MVC 工作原理
  5. 3d翻转 ios_iOS自定义转场详解04——实现3D翻转效果
  6. 播撒汗水,收获希望!
  7. composer 更改为中国镜像
  8. 供参考的 php 学习路线
  9. [转载].程序匠人 - 程序调试(除错)过程中的一些雕虫小技
  10. javaweb(ssh)体育赛事网上售票系统案例
  11. 随笔篇----比特的传输
  12. (转载) Android RecyclerView 使用完全解析 体验艺术般的控件
  13. The Thirty-fourth Of Word-Day
  14. 固态硬盘:NVME 2.0 新技术 ZNS 自动分区:减少延迟,提高寿命
  15. ”什么?穆冉不敢相信地看向穆大海
  16. 智慧零售产业应用实战,30分钟上手的高精度商品识别
  17. 插入排序、希尔排序(Shell)、选择排序、堆排序、冒泡排序、快速排序、归并排序和基数排序(桶排)的 时间复杂度和空间复杂度
  18. 树莓派开机启动python文件_树莓派开机自启动Py文件
  19. 如何批量修改文件名?批量修改照片文件名和添加前缀
  20. 马尔可夫预测matlab编程,MATLAB马尔科夫链预测法

热门文章

  1. 【数据结构算法】递归:八皇后问题
  2. 启动Word 2019时弹出“很抱歉,此功能看似已中断,并需要修复......”
  3. 可以直接用的Excel 宏定义-1
  4. 如何进行高质量的图像标注
  5. BZOJ 1128 [POI2008]Lam 高精度
  6. setSize()和setPreferredSize(new Dimension())使用小结
  7. 强制删除工具Wise Force Deleter v1.49用法
  8. Beyond MapReduce:谈2011年风靡的数据流计算系统
  9. b站会员购独家发售限定手办 率先抢占Z世代消费心智
  10. 联旌智能加入昇腾万里伙伴计划,携手华为共创行业新价值