Android MP3錄製,波形顯示,音頻權限兼容與播放

我又來掀桌子了(ノಠ益ಠ)ノ彡┻━┻,此次是MP3錄製和顯示聲音波形的一個故事。

Demo :github.com/CarGuo/Reco…javascript

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

2

1

  • MP3錄製的邏輯過程就忽略了(真的不是由於懶(。・・)ノ),這裏用的是開源的AndroidMP3Recorder,不過大神太忙了,表示中止維護了,錄製效果還不錯,站在巨人的肩膀上(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的參數,那麼咱們直接降頻就行了。(這波裝的能夠啊~)java

  • 以下面所看到的,其實就一個for循環,首先把音頻數據的長度除了300(哈哈,一會兒少了好多),以後每300個裏面取出最大和最小值(音頻數據是正負數的),目前咱們用的是最大值,這樣把每300個裏面的最大值都存起來,就有了一個降頻後的音頻數據列表了。git

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);
        }
    }
}複製代碼

根據音頻數據繪製波形


一、首先,你得有一個女····呸呸呸,你要有一個View,自定義一個View用來繪製,繪製的邏輯有這麼幾個:

  • 獲取View的大小,取的寬度,高度,還有基線。對基♂線。
  • 生成兩張bitmap做爲雙緩衝繪製,避免閃爍。(別問我爲何,你用一張試試)
  • 對音頻數據塊的數據大小進行判斷,取的縮放係數,作縮放波形處理。
  • 根據控件寬度,間隔係數,從基線開始繪製波形到bitmap,將這個bitmap繪製到顯示的bitmap2。
  • 通知界面顯示將這個bitmap2 繪製到界面上。

是否是很簡單呢?是啊~確實很簡單( ̄o ̄) .那到這裏就結束88咯。github

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

  • 這裏首先經過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
  
  
  

 
  
  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

  • 這個比較簡單,就不廢話了,真的不費話了。就是實現了一個內部定時器,不斷的獲取當前位置,誰讓MediaPlayer原本就支持播放網絡和本地,接口又豐富呢,有興趣demo見。

最後~歡迎來我的的Github網絡

必定要來哈
相關文章
相關標籤/搜索