【Android】pcm音訊資料調節音量大小
1.前言
最近專案裡面有段音訊流需要程式碼控制音量大小,之前是直接推到伺服器端用於語音識別的,由於多裝置同時在執行,會存在串音的問題,因此各裝置在設定頁都增加了一個可滑動進度條,用於動態調解音訊流音量的大小。
2.遇到問題
雖然之前並未接觸過類似的知識點,但在網上找到了 pcm音量控制 這篇文章,文章看起來很高大上,不過我們需要用到的好像就是一個公式而已:

QQ截圖20190119145306.png
就是這麼個東西,具體解釋大家可以點選上面的文章連結看看原理,其中 A1 和 A2 是兩個聲音的振幅,此處的A2為原始音訊振幅,A1為根據所指定db大小計算出來的調節音量後的音訊振幅,畫不多說,試試效果:
int db = -4; private double factor = Math.pow(10,db / 20); //調節PCM資料音量 //pData原始音訊byte陣列,nLen原始音訊byte陣列長度,data2轉換後新音訊byte陣列,nBitsPerSample取樣率,multiple表示Math.pow()返回值 public int amplifyPCMData(byte[] pData, int nLen, byte[] data2, int nBitsPerSample, float multiple) { int nCur = 0; if (16 == nBitsPerSample) { while (nCur < nLen) { short volum = getShort(pData, nCur); volum = (short)(volum * multiple); data2[nCur]= (byte)( volum& 0xFF); data2[nCur+1] = (byte)((volum >> 8) & 0xFF); nCur += 2; } } return 0; } private short getShort(byte[] data, int start) { return (short)((data[start] & 0xFF) | (data[start+1] << 8)); } //把音訊byte[]寫入本地檔案 public static void byte2file(String pathName, byte[] data) { BufferedOutputStream bos = null; File file = null; try { File dir = new File(Environment.getExternalStorageDirectory() + "/test/"); if (!dir.exists()) {//判斷檔案目錄是否存在 dir.mkdirs(); } file = new File(Environment.getExternalStorageDirectory() + "/test/" + pathName); /* 使用以下2行程式碼時,不追加方式*/ /*bos = new BufferedOutputStream(new FileOutputStream(file)); bos.write(bfile); */ /* 使用以下3行程式碼時,追加方式*/ bos = new BufferedOutputStream(new FileOutputStream(file, true)); bos.write(data); bos.flush(); } catch (Exception e) { e.printStackTrace(); } finally { if (bos != null) { try { bos.close(); } catch (IOException e1) { e1.printStackTrace(); } } } }
然後開始測試:
byte[] byteYuanNew = new byte[byteYuan.length]; amplifyPCMData(byteYuan,byteYuan.length,byteYuanNew,16,(float) factor); //存入本地 byte2file("newdata.pcm", byteYuanNew);
通過不斷調整db的值,來生成不同音量的pcm檔案,db為0表示保持音量不變,db為負數表示較低音量,為正數表示提高音量,具體各位自行體會,聲音調節步長最好是2db,然後把pcm音訊檔案匯入到電腦上,用Audacity軟體開啟:

QQ截圖20190119150519.png
我通過調整db的值發現,當-2,-4······-14等等音量並未發生變化,-20突然變小,音量改變有點段落式變化,其實不難發現問題的出現就是在pow這個函式上。
3.解決
Math.pow(64,1/3)等價於 Math.pow(64,0),所以結果是1.0
在程式中,1/3並不代表三分之一,因為這裡是兩個int型別在做除法,結果也是int型別,會自動取整(向下取0了), 所以是0。
但其實我們真正想要的結果是0.3333333333333333333,和0的差距也太大了吧!
其實很簡單的問題,還是java基礎沒打好,知道了問題所在,解決就很簡單了:
private double factor = Math.pow(10, (double)db / 20);
在pow函式裡,指數強轉為double型別,然後資料自然誤差就很小很小了。再次測試:

QQ截圖20190119151712.png
注意:本文並未處理資料溢位的情況,因為每個樣本取值範圍是有限制的,調節音量時不可能隨便增大,比如一個signed 16 bits的樣本,值為5000,我們放大10倍,由於有符號位16bits資料取值範圍為-32768~32767,5000乘以10得到的50000超過了32767,資料溢位了,最後值可能變為-15536,不是我們期望的, 解決方式點我 ;
原始碼: https://github.com/hanxiaofeng/StudyAndroid 工程裡pcmvideotest module!
參考文章:
2. PCM音量控制(高階篇)