PCM
PCM編碼是直接存儲聲波採樣被量化後所產生的非壓縮數據,故被視爲單純的無損耗編碼格式,其優點是可獲得高質量的音頻信號。
PCM是模擬音頻信號經模數轉換(A/D變換)直接形成的二進制序列,該文件沒有附加的文件頭和文件結束標誌。
要將這樣的信號轉爲 PCM 時,需要將聲音量化,我們一般從如下幾個維度描述一段聲音:
1.聲道數 2.採樣位數 3.採樣頻率 4.時長
採樣頻率:即取樣頻率,指每秒鐘取得聲音樣本的次數。採樣頻率越高,聲音的質量也就越好,聲音的還原也就越真實,但同時它佔的資源比較多。由於人耳的分辨率很有限,太高的頻率並不能分辨出來。在16位聲卡中有22KHz、44KHz等幾級,其中,22KHz相當於普通FM廣播的音質,44KHz已相當於CD音質了,目前的常用採樣頻率都不超過48KHz。
採樣位數:即採樣值或取樣值(就是將採樣樣本幅度量化)。它是用來衡量聲音波動變化的一個參數,也可以說是聲卡的分辨率。它的數值越大,分辨率也就越高,所發出聲音的能力越強。
聲道數:很好理解,有單聲道和立體聲之分,單聲道的聲音只能使用一個喇叭發聲(有的也處理成兩個喇叭輸出同一個聲道的聲音),立體聲的PCM 可以使兩個喇叭都發聲(一般左右聲道有分工) ,更能感受到空間效果。
時長: 採樣時長
WAV
WAV爲微軟公司(Microsoft)開發的一種聲音文件格式,它符合RIFF(Resource Interchange File Format)文件規範,用於保存Windows平臺的音頻信息資源,被Windows平臺及其應用程序所廣泛支持,該格式也支持MSADPCM,CCITT A LAW等多種壓縮運算法,支持多種音頻數字,取樣頻率和聲道,標準格式化的WAV文件和CD格式一樣,也是44.1K的取樣頻率,16位量化數字,因此在聲音文件質量和CD相差無幾。
WAVE是錄音時用的標準的WINDOWS文件格式,文件的擴展名爲“WAV”,數據本身的格式爲PCM或壓縮型,屬於無損音樂格式的一種。
Wav File符合 RIFF(Resource Interchange File Format)規範。所有的WAV都有一個文件頭,這個文件頭音頻流的編碼參數。數據塊的記錄方式是little-endian字節順序,標誌符並不是字符串而是單獨的符號。
基於PCM編碼的WAV格式是最基本的WAV格式,被聲卡直接支持,能直接存儲採樣的聲音數據,所存儲的數據能直接通過聲卡播放,還原的波形曲線與原始聲音波形十分接近,播放的聲音質量是一流的,在Windows平臺下被支持得最好,常常被用作在其它編碼的文件之間轉換的中間文件。PCM的缺點是文件體積過大,不適合長時間記錄。正因爲如此,又出現了多種在PCM編碼的基礎上經改進發展起來的編碼格式,如:DPCM,ADPCM編碼等。
WAV和PCM的關係
前面也介紹到了,PCM 數據本身只是一個裸碼流,它是由聲道、採樣位數、採樣頻率、時長共同決定的,因此我們至少要知道其中的三個才能將 PCM 所代表的數據提取出來。因此,純PCM數據是無法播放的,因此還需要一段描述數據。計算機系統中的一個比較常見的做法是將PCM碼流和描述信息封裝在一起,形成一個音頻文件。這樣就可以直接播放了。一種常見的方式是使用wav格式定義的規範將PCM碼流和描述信息封裝起來。查看 PCM 和對應wav文件的 hex(16進制)文件,可以發現,wav文件只是在PCM文件的開頭多了44字節,來表徵其聲道數、採樣頻率和採樣位數等信息。這個其實和bmp非常類似。
經常見到這樣的描述: 44100HZ 16bit stereo 或者 22050HZ 8bit mono 等等.
44100HZ 16bit stereo: 每秒鐘有 44100 次採樣, 採樣數據用 16 位(2字節)記錄, 雙聲道(立體聲);
22050HZ 8bit mono: 每秒鐘有 22050 次採樣, 採樣數據用 8 位(1字節)記錄, 單聲道;
每個採樣數據記錄的是振幅, 採樣精度取決於儲存空間的大小:
1 字節(也就是8bit) 只能記錄 256 個數, 也就是隻能將振幅劃分成 256 個等級;
2 字節(也就是16bit) 可以細到 65536 個數, 這已是 CD 標準了;
4 字節(也就是32bit) 能把振幅細分到 4294967296 個等級, 實在是沒必要了.
如果是雙聲道(stereo), 採樣就是雙份的, 文件也差不多要大一倍。
WAV格式頭實現
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* WavHeader輔助類,用於生成頭部信息
*/
public class WavHeader {
public final char fileID[] = {'R', 'I', 'F', 'F'};
public int fileLength = 0;
public char wavTag[] = {'W', 'A', 'V', 'E'};
public char FmtHdrID[] = {'f', 'm', 't', ' '};
public int FmtHdrLeth = 16;
public short FormatTag = 1;
public short Channels = 1;
public int SamplesPerSec = 44100;
public int AvgBytesPerSec = 88200;
public short BlockAlign = 2;
public short BitsPerSample = 16;
public char DataHdrID[] = {'d','a','t','a'};
public int DataHdrLeth = 0;
public byte[] getHeader() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
WriteChar(bos, fileID);
WriteInt(bos, fileLength);
WriteChar(bos, wavTag);
WriteChar(bos, FmtHdrID);
WriteInt(bos,FmtHdrLeth);
WriteShort(bos,FormatTag);
WriteShort(bos,Channels);
WriteInt(bos,SamplesPerSec);
WriteInt(bos,AvgBytesPerSec);
WriteShort(bos,BlockAlign);
WriteShort(bos,BitsPerSample);
WriteChar(bos,DataHdrID);
WriteInt(bos,DataHdrLeth);
bos.flush();
byte[] r = bos.toByteArray();
bos.close();
return r;
}
private void WriteShort(ByteArrayOutputStream bos, int s) throws IOException {
byte[] mybyte = new byte[2];
mybyte[1] =(byte)( (s << 16) >> 24 );
mybyte[0] =(byte)( (s << 24) >> 24 );
bos.write(mybyte);
}
private void WriteInt(ByteArrayOutputStream bos, int n) throws IOException {
byte[] buf = new byte[4];
buf[3] =(byte)( n >> 24 );
buf[2] =(byte)( (n << 8) >> 24 );
buf[1] =(byte)( (n << 16) >> 24 );
buf[0] =(byte)( (n << 24) >> 24 );
bos.write(buf);
}
private void WriteChar(ByteArrayOutputStream bos, char[] id) {
for (int i=0; i<id.length; i++) {
char c = id[i];
bos.write(c);
}
}
}
參考鏈接:
https://www.jianshu.com/p/638fa13082eb
https://www.cnblogs.com/ranson7zop/p/7657874.html
https://www.cnblogs.com/wanggang123/p/5589488.html
https://blog.csdn.net/zhuweigangzwg/article/details/51499123