早在上大學的時候,就想設計一個網路音頻播放器,可以點歌,由於技術、懶散等原因,一直沒有去實現(這裡說的技術原因指的是對音頻API不熟悉,使用WM控制項或者DShow等高科技不在本文討論範圍內,本文主要討論wave系列函數來實現音頻播放)。
這幾天因為項目需要,有機會全身心去研究wave系列函數,雖然截至目前仍沒完全搞清楚其內部原理,不過對於區域網路流暢播放音頻,基本上沒什麼問題了。
對於網路音頻播放,緩衝是在所難免的,我設計的是緩衝3秒資料,迴圈覆蓋(意思是緩衝區是3秒音頻資料的大小,從網路收到的資料迴圈寫入緩衝區,比如300K緩衝,每次網路接收40K,當緩衝區裝了280K的時候,再次接收資料時,就把20K存入到緩衝區的尾部,然後剩下的20K存入緩衝區頭部,下次接收從緩衝區20K的地方開始儲存)。
為了音頻播放流程,線程從檔案讀取音頻資料或播放音訊節奏很重要,為了簡單方便,我是這樣設計的:線程從檔案每次讀取8分之一秒音頻大小,讀取間隔休眠120毫秒,同樣,播放音頻時,設計的是8個緩衝區,每個緩衝區的大小也是8分之一秒音訊大小,然後休眠120毫秒,這樣聽起來基本上感覺還可以。
最後簡單描述一下程式流程:服務端,當接收到用戶端串連時,首選從指定的音頻檔案讀取頭部資訊發給用戶端,然後每次讀取8分之一秒音頻大小的資料發給用戶端,每次發送間隔120毫秒。用戶端,連上伺服器後,首先接收音頻頭資料,初始化waveout,然後不停接收音頻資料存入緩衝區,在放音線程裡面,當發現播放的音頻塊個數小於接收的音頻塊個數,就從緩衝區複製音頻資料來播放,每次播放休眠120毫秒。
下面是從網上弄的音頻檔案頭部結構體:
#pragma pack( 1 )
typedef struct _WaveFileHeader
{
char cChunkID[4]; // R I F F
int nChunkSize; // FileSize - 8 bytes
char cFormat[4]; // W A V E
char cSubChunk1ID[4]; // F M T
int nSubChunk1Size; // this struct size - 8 bytes
short sAudioFormat; // PCM
short sNumChannels; // numbers of channel
int nSampleRate; // Samples per second
int nBytesRate; // bytes per second
short sBlockAlign; // align of block
short sBitsPerSample; // bits per sample
char cSubChunk2ID[4]; // d a t a
int nSubChunk2Size;
}WaveFileHeader;
#pragma pack()
這裡是示範程式的:下載
原始碼:下載
後記:今天聽歌的時候,意外發現放歌有點噪音,反覆檢查代碼,沒什麼問題,後來才意識到是設計的缺陷,如果音頻採樣率很特殊,造成每秒的資料率除以8後,不能保持資料對齊(sBlockAlign),就會有噪音,問題已修正。
.