自定义控件之动态声纹波形图实现
来源:互联网 发布:临床试验数据库软件 编辑:程序博客网 时间:2024/06/06 14:02
大家应该都见过波形图,生活中很多仪表上面都有。例如心电机,录音软件,甚至KTV里面的唱歌系统。用ios系统的人应该用过录音软件,就有这样的波形图。因项目需求,研究了很久,用android代码做个波形图控件。最繁琐的是计算的部分。
1、首先波形图的高低代表数值的大小,数值来源是什么。可以是音量,可以是你自定义的任何属性数值。所以这个自定义控件对外暴露的就是设置数值的接口。
2、如何做一个会动的波形图,实时绘制的波形图,第一个想到的是定时任务,不断的绘制数据到页面上,可是波形图后续的数据是要替换前面的,如何做出波形图的数据在往前移动的效果。用Cavus就可以做到。
贴代码:
数据来源计算:
/** * 200ms 一次 回调音频数据 分割成20份byte[]数据 * 每10ms算一次音量 * * @param audioData */ public void plusAudioData(byte[] audioData) { if (mOffsetDistance == 0) { mOffsetDistance = ((float) mWidthSpecSize) / ((float) (TimeStep.ShortSentence.getValue() / mRefreshInterval)); } if (mOffsetDistance > splitCount) { splitCount = (int) (mOffsetDistance + 1); //4.0分辨率高 splitCount必须大于mOffsetDistance 不然会有空白区域 } LogUtils.e("ljx", " splitCount : " + splitCount); if (audioData != null && audioData.length > splitCount) { int splitPart = (int) Math.floor(((float) (audioData.length)) / (float) splitCount); int[] volumes = new int[splitCount]; for (int i = 0; i < splitCount; i++) { byte[] tempAudioData = new byte[splitPart]; for (int j = 0; j < splitCount; j++) { tempAudioData[j] = audioData[i * splitPart + j]; } volumes[i] = getVolume(tempAudioData, tempAudioData.length); } addData(volumes); LogUtils.e("ljx", "plusAudioData ================================>>>>>>>>>>>>>> 200ms 等分" + splitCount + "个数据"); } else { int[] volumes = new int[splitCount]; addData(volumes); } }外部调用根据你录音说话,不断产生数据传值进来,这里我用的三方的语音sdk返回的实时音量数据作为波形图的数据绘制的,遇到一个问题是,三方返回的频率是200ms一次平均音量值,1s只有5次采集,这样远远不够绘制波形图,会让波形图特别的粗,因为200ms绘制等宽的view,数据填充不够。 后来改成了,根据三方返回的音频文件pcm格式的最原始音频数据,自己计算音量。这里我默认是200ms采集20次音量,需要将pcm 数据等分成20分然后计算每份的音量均值。然后遇到个问题是对于分辨率高的设备20等分还是数据不够,于是offsetDistance是200ms绘制的像素宽度,如果<20,则等分20份,如果>20则等分像素宽度份。这样就不会缺少数据了。
计算音量的方法:(绝密,这个是不能贴的)如果你们用的别的数据来源是不需要我这么算的。
/** * 根绝 byte[] pcm音频数据 算出实时音量大小 * * @param buffer * @param len * @return */ private int getVolume(byte[] buffer, int len) { int value = 0; float energy = 0, tmp = 0; for (int i = 0; i < len && i + 1 < len; i += 2) { int v1 = buffer[i] & 0xFF; int v2 = buffer[i + 1] & 0xFF; short bufShort = (short) (v1 + (v2 << 8)); tmp += bufShort; energy += bufShort * bufShort; } energy = energy / len - (tmp / len) * (tmp / len); value = (int) (Math.pow(energy, 0.2f) * 2); if (value < 0) value = 0; if (value > 100) value = 100; return value; }
绘制线程:
/** * 绘制波形图线程task * 定时任务 */ class DrawThreadTask extends Thread { @SuppressWarnings("unchecked") @Override public void run() { LogUtils.e("ljx", "200ms定时刷新====>>>数据池个数" + mRecDataList.size() + " ====>>>刷新数据位置" + (mAlreadyDrawDataPosition + 1)); LogUtils.e("ljx", "Visibility :" + getVisibility()); if (mBitmap == null) { LogUtils.e("ljx", "mBackgroundBitmap == null"); return; } LogUtils.e("ljx", "mHeightSpecSize :" + mHeightSpecSize + " mWidthSpecSize :" + mWidthSpecSize); if (mCanvas != null) { if (mOffsetDistance == 0) { mOffsetDistance = ((float) mWidthSpecSize) / ((float) (TimeStep.ShortSentence.getValue() / mRefreshInterval)); LogUtils.e("ljx", "mOffsetDistance : " + mOffsetDistance); } if (mRecDataList.size() <= 0 || mRecDataList.size() < mAlreadyDrawDataPosition + 1) { return; } if (mAlreadyDrawDataPosition >= TimeStep.ShortSentence.getValue() / mRefreshInterval) { //已经刷到波形图边缘 让波形图动起来 mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); int startP = mAlreadyDrawDataPosition - TimeStep.ShortSentence.getValue() / mRefreshInterval + 1; for (int i = startP; i <= mAlreadyDrawDataPosition; i++) { int[] newData = mRecDataList.get(i);// LogUtils.e("ljx","============开始区块================="); for (int j = 0; j < newData.length; j++) { float boHeight = Math.abs(((float) newData[j]) / 100f * mBaseLine); float max = mBaseLine - boHeight * mScale; float min = mBaseLine + boHeight * mScale; max = max <= 0 ? 0 : max; min = min >= 2 * mBaseLine ? 2 * mBaseLine : min; float startX = (i - startP) * mOffsetDistance + (((float) (j + 1) / (float) newData.length)) * mOffsetDistance; mCanvas.drawLine(startX, min, startX, max, mPaint);// LogUtils.e("ljx","====startX :"+startX); }// LogUtils.e("ljx","============结束区块================="); } } else { int[] newData = mRecDataList.get(mAlreadyDrawDataPosition); for (int i = 0; i < newData.length; i++) { float boHeight = Math.abs(((float) newData[i]) / 100f * mBaseLine); float max = mBaseLine - boHeight * mScale; float min = mBaseLine + boHeight * mScale; max = max <= 0 ? 0 : max; min = min >= 2 * mBaseLine ? 2 * mBaseLine : min; float startX = mAlreadyDrawDataPosition * mOffsetDistance + (((float) (i + 1) / (float) newData.length)) * mOffsetDistance; mCanvas.drawLine(startX, min, startX, max, mPaint); } } mAlreadyDrawDataPosition++; } mHandler.removeMessages(0); mHandler.sendEmptyMessage(0);// mHandler.post(new Runnable() {// @Override// public void run() {// invalidate();// }// }); } }
这是绘制的线程task,原理是这样的设置view的ViewTreeObserver,得到自定义控件的宽高,例如每200ms刷新一次view,向前绘制20条数据,那20条数据绘制多宽,这里用控件的宽/固定的时间。这个时间是我设置绘制到头需要12s的时间,超过12s的数据就开始动起来。如何动起来就是,这里我用AlreadyDrawPosition记录了绘制的数据位置,<12s只向前绘制一份数据,>20s则绘制从AlreadyDrawPosition-12s/200ms位置到AlreadyDrawPosition的数据。这样就会肉眼看上去波形图是在往前移动的。
开始绘制:
/** * 开始绘制 */ public void startView() { if (mDrawTask != null && mExecutor != null && mDrawTask.isAlive()) { mExecutor.shutdownNow(); while (mExecutor.isTerminated()) ; LogUtils.e("ljx", "stopView====>>>again "); mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); } mAlreadyDrawDataPosition = 0; mExecutor = new ScheduledThreadPoolExecutor(2); mDrawTask = new DrawThreadTask(); mExecutor.scheduleWithFixedDelay(mDrawTask, 0, mRefreshInterval, TimeUnit.MILLISECONDS); }
停止绘制:
/** * 停止绘制 */ public void stopView() { mRecDataList.clear(); mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); if (mExecutor != null) { mExecutor.shutdown(); } mExecutor = null; mDrawTask = null; mAlreadyDrawDataPosition = 0; }
贴效果图:
阅读全文
0 0
- 自定义控件之动态声纹波形图实现
- 安卓上实现的自定义心电波形控件
- 状态波形图控件
- VisualC++实现的函数波形观察控件
- VC实现波形不闪烁动态绘图
- VC实现波形不闪烁动态绘图
- VC实现波形不闪烁动态绘图
- Android自定义组合控件实现动态轮播图
- Android自定义控件之动态柱状图
- 自定义控件之动态进度View
- 纯js实现波形图
- C#实现wav波形图
- 一个C#编写的开源用户自定义控件—野比的状态波形图控件
- 基于.net平台的自定义绘制波形控件
- 自定义view动态加载控件实现动态换行
- 基于CStatic的波形曲线控件的实现
- 基于CStatic的波形曲线控件的实现
- C#用serialPort和chart控件实现简单波形绘制
- Vue.js 2.X DEMO以及安装
- Linux主从复制
- C/C++ 第四周线性表(二)-- 项目三 单链表的应用(2)
- java 数据流读取文件
- ABP官方文档(二十二)【规约模式】
- 自定义控件之动态声纹波形图实现
- Java Could not publish server configuration 错误处理
- 每过3秒钟更新下一条点评头条的内容
- python学习笔记1-numpy/enumerate
- Ecshop 2.7.1 B2B2C 小京东 商城网站 商品详情页二维码显示出错
- HM编码器代码阅读(7)——整个编码流程以及相关的函数
- springmvc和spring注解扫描注意事项
- python 3.6 安装 Twisted 错误与解决
- 软件集成接口.COM组件交互(1)