美文网首页安卓开发笔记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%,在线等
        哇塞哇哇塞:大神,我想让声波从左向右动,我该怎么修改,麻烦大神告诉我: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