早在上大学的时候,就想设计一个网络音频播放器,可以点歌,由于技术、懒散等原因,一直没有去实现(这里说的技术原因指的是对音频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),就会有噪音,问题已修正。
.