博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android MP3录制,波形显示,音频权限兼容与播放
阅读量:6699 次
发布时间:2019-06-25

本文共 5765 字,大约阅读时间需要 19 分钟。

我又来掀桌子了(ノಠ益ಠ)ノ彡┻━┻,这次是MP3录制和显示声音波形的一个故事。

Demo :

先看看效果┑( ̄Д  ̄)┍

2

1
  • 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(ArrayList list) {    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见。

最后~欢迎来个人的

一定要来哈

转载地址:http://dcwlo.baihongyu.com/

你可能感兴趣的文章
textView限制字数(超简单,不走弯路)(解决联想输入及iOS7崩溃等问题)
查看>>
shell实例
查看>>
我的友情链接
查看>>
java中四种进制的转换
查看>>
git多个远程仓库
查看>>
Linux之命令
查看>>
Android 6.0 特性
查看>>
shell 脚本作业
查看>>
程序员老司机都要错的 Python 陷阱与缺陷列表
查看>>
《netty入门与实战》笔记-06:心跳与空闲检测
查看>>
使用javascript开发的视差滚动效果的云彩 极客标签 - 做最棒的极客知识分享平台...
查看>>
SSM整合框架
查看>>
【安全牛学习笔记】CONTROL FRAME
查看>>
信号量 互斥锁 条件变量的区别(讲的很好,值得收藏)
查看>>
Linux学习笔记十二周四次课(4月26日)
查看>>
Linux之用户管理
查看>>
写一个shell脚本,把192.168.0.0/24网段在线的ip列出来。 思路: for循环, 0
查看>>
JDK安装与环境变量配置
查看>>
多字符与宽字符相互之间的转换
查看>>
CentOS 7.3 mini 解压安装二进制 mariadb 10.2.10
查看>>