美文网首页安卓开发笔记Android的数据处理方案音视频
Android MP3录制,波形显示,音频权限兼容与播放

Android MP3录制,波形显示,音频权限兼容与播放

作者: 恋猫月亮 | 来源:发表于2016-11-15 20:56 被阅读8883次
我又来掀桌子了(ノಠ益ಠ)ノ彡┻━┻,这次是MP3录制和显示声音波形的一个故事。

</p>
Demo :https://github.com/CarGuo/RecordWave

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

2 1
  •  MP3录制的逻辑过程就忽略了(真的不是因为懒(。・・)ノ),这里用的是开源的<a href="https://github.com/GavinCT/AndroidMP3Recorder">AndroidMP3Recorder</a>,不过大神太忙了,表示停止维护了,录制效果还不错,站在巨人的肩膀上(cao xi),增加了音频录制的权限判断和数据提取的逻辑。<a href="http://www.cnblogs.com/ct2011/p/4080193.html">关于录制的请戳这里传送</a>。
    

增加权限提示

</p>

  •  在音频相关开发的时候,经常涉及到权限的问题,而6.0以下的时候是没有接口判断的,如何敏感的权限,为啥子没有呢(ノಠ益ಠ)ノ彡┻━┻,所以经常会和camera,拍摄视频一样,通过try这个crash来判断吧。
    
  •  如下在startRecording的时候把crash捕获了(让你崩溃(ノಠ益ಠ)ノ彡┻━┻),之后通过判断int readSize = mAudioRecord.read(mPCMBuffer, 0, mBufferSize);读出来的是<a>,ERROR_INVALID_OPERATION </a>,<a>ERROR_BAD_VALUE</a>,的话,那么就通知你的页面:<a>这货不给权限还想录音,逗我吗</a>···然后退出录制。
    
···
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);
        }
    }
}

根据音频数据绘制波形

</p>
1、首先,你得有一个女····呸呸呸,你要有一个View,自定义一个View用来绘制,绘制的逻辑有这么几个:

  • 获取View的大小,取的宽度,高度,还有基线。对基♂线。
  • 生成两张bitmap作为双缓冲绘制,避免闪烁。(别问我为什么,你用一张试试)
  • 对音频数据块的数据大小进行判断,取的缩放系数,做缩放波形处理。
  • 根据控件宽度,间隔系数,从基线开始绘制波形到bitmap,将这个bitmap绘制到显示的bitmap2。
  • 通知界面显示将这个bitmap2 绘制到界面上。

是不是很简单呢?是啊~确实很简单( ̄o ̄) .那到这里就结束88咯。

咦,下面还有啊 (ˉ▽ ̄~) 切~~

  •  这里首先通过onVisibilityChanged,在里面注册了一个addOnPreDrawListener,在addOnPreDrawListener里面获取到了view的大小、基线,生成两张需要的bitmap。
    
@Override
protected 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<Short> 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);
}

···
@Override
protected void onDraw(Canvas c) {
    super.onDraw(c);
    if (mIsDraw && mBitmap != null) {
        synchronized (mLock) {
            c.drawBitmap(mBitmap, 0, 0, mViewPaint);
        }
    }
}

音频播放AudioPlayer

</p>

  • 这个比较简单,就不废话了,真的不费话了。就是实现了一个内部定时器,不断的获取当前位置,谁让MediaPlayer本来就支持播放网络和本地,接口又丰富呢,有兴趣demo见。

最后~欢迎来个人的<a href="https://github.com/CarGuo">Github</a>

一定要来哈

相关文章

网友评论

  • 90df3cf952b6:在8.1的系统上不行,其他版本可能也有问题,在录音的过程中一直有warning
    W/System.err: at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1227)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:347)
    at com.shuyu.waveview.AudioWaveView.deepCopy(AudioWaveView.java:296)
    at com.shuyu.waveview.AudioWaveView$drawThread.run(AudioWaveView.java:209)
    点停止,再点播放的话就崩溃
    V/MediaPlayer: resetDrmState: mDrmInfo=null mDrmProvisioningThread=null mPrepareDrmInProgress=false mActiveDrmScheme=false
    cleanDrmObj: mDrmObj=null mDrmSessionId=null

    E/AndroidRuntime: FATAL EXCEPTION: Timer-0
    Process: com.shuyu.app, PID: 5614
    java.lang.IllegalStateException
    at android.media.MediaPlayer.isPlaying(Native Method)
    at com.shuyu.waveview.AudioPlayer$1.run(AudioPlayer.java:90)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)
    90df3cf952b6:出的是 W/System.err: java.util.ConcurrentModificationException
    at java.util.ArrayList.writeObject(ArrayList.java:766)
  • Okamiy:问下你这个录音怎么转的mp3格式,直接保存为MP3?只是后缀改了?
    Okamiy:@恋猫月亮 github上的报错,个人只会Android那个错看着好眼生,楼主有空吗?542839122,加个qq指点一下,
    恋猫月亮:@Okamiy pcm 转码 MP3,用库
  • LeaYw:再采样之后,datalist也没用再用啊,有什么用
    LeaYw:额,找到怎么调用了,怎么不再采样成功之后通知AudioWaveView draw。你这样写法有点奇怪啊
  • 1699579611bd:请问一下 支持边录边播么
  • 我一定会学会:大佬,你这个进群还要钱,能不能拉我进去啊
  • 正规程序员:不用Lame的pcm转mp3的链接有吗?
  • Blazer:你好,麻烦问下,播放音频时如何关闭波形颜色的变化呢?
  • Blazer:你好,博主,引入了您这个库,需要做特殊的混淆处理吗?
    Blazer:@恋猫月亮 okay,thanks
    恋猫月亮:@Blazer 不要混淆view的继承就可以,理论上是不需要
  • 老科不弱于人:我想问下,第一次进来,没有声音的情况下,为什么线画的很高,只有录音发出声音后,它就正常了, 还有怎么清除已经画的竖线呢? 望大神帮忙解答下,感激不尽!
    老科不弱于人:@恋猫月亮 惭愧,对自定义view这一块不是很了解,望大神教教清除之前所有的绘制怎么做,因为正常的逻辑是录完一个音保存后,波形总不能接着上次的来,应该重新开始绘制。:pray:
    恋猫月亮:@老科不弱于人 清除的话有clear接口吧?具体看代码,然后线高度适合当前数据的最大值有关系
  • 路途等待:能不能加一个录制的最大时间呢
  • bc8fd89ced61:楼主为什么我这运行直接报错呀!
    恋猫月亮: @Abigale_8ce7 工作时间上不了外网,我只弄手机看,你加了录音权限没?有错误log吗
    bc8fd89ced61:@恋猫月亮 直接点击录音就崩溃了。读写本地权限我也添加了。我加那个QQ群了,群主是你吗?可以QQ聊吗?
    恋猫月亮: @Abigale_8ce7 报什么错
  • sorgs:下载demo根本就不能运行啊
    恋猫月亮: @sorgs_0414 没发现 问题
  • 小柏不是大白:为啥波形图出不来
    恋猫月亮: @小柏不是大白 demo中出不来?
  • Silence潇湘夜雨:LZ:你好,请问这个可以加载网络地址,直接播放语音吗?
    恋猫月亮: @Silence潇湘夜雨 可以
  • null_js:录制暂停,然后直接播放怎么实现?
  • e0278c521a0a:你好,播放.wav格式音频红米note4 会出现卡顿播放。
    恋猫月亮: @ZzGhY 播放wav会卡顿?本地的吗?
  • 61d45bd09684:边播边缓存?是不是说播放过的 就会下载好了?怎么做到的?
    MrHu_9361:请问可以实现音频的剪辑及预览吗?最近项目要用,可以推荐你点资料吗?
    61d45bd09684:@恋猫月亮 看到了,videocache这个框架。果然还是只有这个才能实现···
    恋猫月亮: @61d45bd09684 本地增加代理实现,代理负责拉流,分发给播放器。
  • 醉淸风:录音能获取麦克风的音量大小吗?
    恋猫月亮: @醉淸风 可以,就是分贝
  • 567dacd0f6dd:大神,怎么对比2个音频波形的相似度呢,完全相似用100%,一半用50%,在线等
    c1079828ae14:大神,我想让声波从左向右动,我该怎么修改,麻烦大神告诉我:stuck_out_tongue_closed_eyes:
    恋猫月亮: @音符跳跃思念每天 你这个得从数据源入手丫,比如对比两个音频数据的峰值频率,自相关计算每分钟的峰值数,还有包络之类的,这个还真不好说。
  • 00c934a70b2c:android 7.0 录音时一直提示没有权限,看代码是获取的readsize为0,但是权限已经给了的
    恋猫月亮: @MonTrsor 是否有安全中心禁止了权限
  • 367c2b1d346e:我把github上的最新的demo装在手机上之后,用了几次,有的时候点击录音按钮突然崩了,报数组越界,就是AudioWaveView.java:258行越界,不知道是什么原因。大神可否解释一下。好像和那个波形图有关。
  • 232caef1976e:在悬浮窗中,图形没有绘制出来,不知道原因
    恋猫月亮:@00236 断点调试下bitmap是否创建
  • 卷卷更健康:波形图可以滑动就更好了:relaxed:
  • Zack_zhou:你还,这个我测试用了一下,有一个问题,就是如果突然遇到一个很大的声音,那么波形图的比例尺会放大,但是当声音回归正常,这些波形会显得非常小,不是很美观,尝试去改了下,(比如当声音超过某个阈值后,不会无限制的放大比例尺)但是没找到那里改,我水平太差了,能不能给个指点,在哪里改?
    Zack_zhou:找到了:stuck_out_tongue:
  • 8ff6bbf32ad6:我来帮你测试一下
  • 恋猫月亮:关于播放显示波形和根据颜色特意闪烁 http://www.jianshu.com/p/82ed1dc39a13
  • Mzone:只拿走女朋友 ( ̄o ̄)
    恋猫月亮: @灬帅气的昵称丶 如花给你
  • 恋猫月亮:1.0.2 声音录制增加了新的功能,下一期把新的两个功能也讲一下
  • 恋猫月亮:增加了1.0.1版本,添加了播放MP3文件也可以显示波形
    2d3725d1f0a8:这个要是能实时显示的效果和WINDOWS MEDIA PLAYER一样就好了
  • Hidetag:只拿走波形图
    流穿枫:@恋猫月亮 首先要有个女朋友
    恋猫月亮: @S_H_I_E_L_D 只要不是女朋友,什么都可以拿走🤗
  • Jooyer:我去测试哈,嘿嘿
    恋猫月亮:@浪漫随风 欢迎欢迎

本文标题:Android MP3录制,波形显示,音频权限兼容与播放

本文链接:https://www.haomeiwen.com/subject/naklpttx.html