Android MP3录制,波形显示,音频权限兼容与播放
发布日期:2021-06-29 12:49:27
浏览次数:2
分类:技术文章
本文共 5774 字,大约阅读时间需要 19 分钟。
我又来掀桌子了(ノಠ益ಠ)ノ彡┻━┻,这次是MP3录制和显示声音波形的一个故事。
Demo :
先看看效果┑( ̄Д  ̄)┍
MP3录制的逻辑过程就忽略了(真的不是因为懒(。・・)ノ),这里用的是开源的,不过大神太忙了,表示停止维护了,录制效果还不错,站在巨人的肩膀上(cao xi),增加了音频录制的权限判断和数据提取的逻辑。。
增加权限提示
在音频相关开发的时候,经常涉及到权限的问题,而6.0以下的时候是没有接口判断的,如何敏感的权限,为啥子没有呢(ノಠ益ಠ)ノ彡┻━┻,所以经常会和camera,拍摄视频一样,通过try这个crash来判断吧。 如下在startRecording的时候把crash捕获了(让你崩溃(ノಠ益ಠ)ノ彡┻━┻),之后通过判断int readSize = mAudioRecord.read(mPCMBuffer, 0, mBufferSize);读出来的是,ERROR_INVALID_OPERATION ,ERROR_BAD_VALUE,的话,那么就通知你的页面:这货不给权限还想录音,逗我吗···然后退出录制。
···try { mAudioRecord.startRecording();} catch (Exception ex) { ex.printStackTrace();}···while (mIsRecording) { int readSize = mAudioRecord.read(mPCMBuffer, 0, mBufferSize); if (readSize == AudioRecord.ERROR_INVALID_OPERATION || readSize == AudioRecord.ERROR_BAD_VALUE) { if (errorHandler != null && !sendError) { sendError = true; errorHandler.sendEmptyMessage(ERROR_TYPE); mIsRecording = false; isError = true; } } else { if (readSize > 0) { mEncodeThread.addTask(mPCMBuffer, readSize); calculateRealVolume(mPCMBuffer, readSize); sendData(mPCMBuffer, readSize); } else { if (errorHandler != null && !sendError) { sendError = true; errorHandler.sendEmptyMessage(ERROR_TYPE); mIsRecording = false; isError = true; } } }}
再采样(这个词看起来很高端的样子)
其实就是音频数据进行缩减,因为手机每次读上来的音频数据是比较大的,这和我们配置的录制参数也有关系,这里使用的是44100HZ的参数,那么我们直接降频就好了。(这波装的可以啊~)
如下面所看到的,其实就一个for循环,首先把音频数据的长度除了300(哈哈,一下子少了好多),之后每300个里面取出最大和最小值(音频数据是正负数的),目前我们用的是最大值,这样把每300个里面的最大值都存起来,就有了一个降频后的音频数据列表了。
private void sendData(short[] shorts, int readSize) { if (dataList != null) { int length = readSize / 300; short resultMax = 0, resultMin = 0; for (short i = 0, k = 0; i < length; i++, k += 300) { for (short j = k, max = 0, min = 1000; j < k + 300; j++) { if (shorts[j] > max) { max = shorts[j]; resultMax = max; } else if (shorts[j] < min) { min = shorts[j]; resultMin = min; } } if (dataList.size() > maxSize) { dataList.remove(0); } dataList.add(resultMax); } }}
根据音频数据绘制波形
1、首先,你得有一个女····呸呸呸,你要有一个View,自定义一个View用来绘制,绘制的逻辑有这么几个:
- 获取View的大小,取的宽度,高度,还有基线。对基♂线。
- 生成两张bitmap作为双缓冲绘制,避免闪烁。(别问我为什么,你用一张试试)
- 对音频数据块的数据大小进行判断,取的缩放系数,做缩放波形处理。
- 根据控件宽度,间隔系数,从基线开始绘制波形到bitmap,将这个bitmap绘制到显示的bitmap2。
- 通知界面显示将这个bitmap2 绘制到界面上。
是不是很简单呢?是啊~确实很简单( ̄o ̄) .那到这里就结束88咯。
咦,下面还有啊 (ˉ▽ ̄~) 切~~
这里首先通过onVisibilityChanged,在里面注册了一个addOnPreDrawListener,在addOnPreDrawListener里面获取到了view的大小、基线,生成两张需要的bitmap。
@Overrideprotected void onVisibilityChanged(@NonNull View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (visibility == VISIBLE && mBackgroundBitmap == null) { ViewTreeObserver vto = getViewTreeObserver(); vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { if (getWidth() > 0 && getHeight() > 0) { mWidthSpecSize = getWidth(); mHeightSpecSize = getHeight(); mBaseLine = mHeightSpecSize / 2; mBackgroundBitmap = Bitmap.createBitmap(mWidthSpecSize, mHeightSpecSize, Bitmap.Config.ARGB_8888); mBitmap = Bitmap.createBitmap(mWidthSpecSize, mHeightSpecSize, Bitmap.Config.ARGB_8888); mBackCanVans.setBitmap(mBackgroundBitmap); mCanvas.setBitmap(mBitmap); ViewTreeObserver vto = getViewTreeObserver(); vto.removeOnPreDrawListener(this); } return true; } }); }}
音频数据是一块一块的list过来的,所以获取每个块中最大值,与当前view对比,就知道这一块数据需要缩放的系数啦。
/** * 根据当前块数据来判断缩放音频显示的比例 * * @param list 音频数据 */private void resolveToWaveData(ArrayListlist) { short allMax = 0; for (Short sh : list) { if (sh > allMax) { allMax = sh; } } int curScale = allMax / mBaseLine; if (curScale > mScale) { mScale = ((curScale == 0) ? 1 : curScale); }}
开始绘制到bitmap上啦~
首先把bitmap洗干净drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);,然后绘制基线,接着就是for循环绘制音频线了。 绘制以基线为轴,每个音频数据除以缩放系数(需要考虑乘的情况?反正我bu care~),然后绘制上下的竖线,之后把这个bitmap绘制到要完整显示的mBackgroundBitmap,最后通知界面更新(发到UI线程),然后界面把要显示的mBackgroundBitmap贴出来。if (mBackCanVans != null) { //洗白白 mBackCanVans.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); mBackCanVans.drawLine(0, mBaseLine, mWidthSpecSize, mBaseLine, mPaint); int drawBufsize = dataList.size(); //判断大小,是否改变显示的比例 for (int i = 0, j = 0; i < drawBufsize; i++, j += mOffset) { Short sh = dataList.get(i); short max = (short) (mBaseLine - sh / mScale); short min; if (mWaveCount == 2) { min = (short) (sh / mScale + mBaseLine); } else { min = (short) (mBaseLine); } mBackCanVans.drawLine(j, mBaseLine, j, max, mPaint); mBackCanVans.drawLine(j, min, j, mBaseLine, mPaint); } synchronized (mLock) { mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); mCanvas.drawBitmap(mBackgroundBitmap, 0, 0, mPaint); } Message msg = new Message(); msg.what = 0; handler.sendMessage(msg);}···@Overrideprotected void onDraw(Canvas c) { super.onDraw(c); if (mIsDraw && mBitmap != null) { synchronized (mLock) { c.drawBitmap(mBitmap, 0, 0, mViewPaint); } }}
音频播放AudioPlayer
这个比较简单,就不废话了,真的不费话了。就是实现了一个内部定时器,不断的获取当前位置,谁让MediaPlayer本来就支持播放网络和本地,接口又丰富呢,有兴趣demo见。
最后~欢迎来个人的
转载地址:https://carguo.blog.csdn.net/article/details/53900627 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
很好
[***.229.124.182]2024年04月15日 05时07分53秒
关于作者
喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
电气毕业生在国家电网都干啥工作?
2019-04-29
为什么LED灯会越用越暗?
2019-04-29
为什么说卷积神经网络,是深度学习算法应用最成功的领域之一?
2019-04-29
在电网工作,有多高大上?
2019-04-29
「2020年大学生电子设计竞赛分享」电源题,省一等奖!
2019-04-29
又一国产开源微内核操作系统上线!源代码已开放下载
2019-04-29
10年老兵!从大学毕业生到嵌入式系统工程师的修炼之道……
2019-04-29
如何才能学好单片机?
2019-04-29
一根网线有这么多“花样”,你知道吗?
2019-04-29
雷军1994年写的诗一样的代码,我把它运行起来了!
2019-04-29
2020年大学生电子设计竞赛,B题,单相在线式不间断电源,详细技术方案!
2019-04-29
大佬终于把鸿蒙OS讲明白了,收藏了!
2019-04-29
C语言指针,这可能是史上最干最全的讲解啦(附代码)!!!
2019-04-29
国内大陆有哪些芯片公司处于世界前10?一起看看!
2019-04-29
单精度、双精度、多精度和混合精度计算的区别是什么?
2019-04-29
中国35位“大国工匠”榜单出炉!西工大、西电合计占半壁江山!清华仅1人!...
2019-04-29
知乎热议:嵌入式开发中C++好用吗?
2019-04-29
2020,Python 已死?
2019-04-29
漫画:程序员相亲?哈哈哈哈哈哈
2019-04-29
30种EMC标准电路分享,再不收藏就晚了!
2019-04-29