WaveOut是windows自带的一个播放接口,具体是啥就不介绍了,先把使用它来播放wav文件或者mp3文件,其实最终还是pcm格式的音频吧。
WaveOut的使用很简单,一个是设置好音频的描述,比如位宽、声道数、频率,二是装好数据。就可以达到播放的效果。音频的参数设置见头文件MMSystem.h定义
typedef struct tWAVEFORMATEX
{
WORD wFormatTag; /* format type */
WORD nChannels; /* number of channels (i.e. mono, stereo...) */
DWORD nSamplesPerSec; /* sample rate */
DWORD nAvgBytesPerSec; /* for buffer estimation */
WORD nBlockAlign; /* block size of data */
WORD wBitsPerSample; /* number of bits per sample of mono data */
WORD cbSize; /* the count in bytes of the size of */
/* extra information (after cbSize) */
} WAVEFORMATEX, *PWAVEFORMATEX, NEAR *NPWAVEFORMATEX, FAR *LPWAVEFORMATEX;
想了解下格式的话可以在百度百科中搜wav,除去文件头接下来的就和上面定义的一样了。
播放只需要调用windows的api即可完成。使用waveOutOpen打开驱动设备,这里可以设置和状态有关的回调函数;准备好了WAVEHDR(格式以及数据填充)之后使用waveOutWrite写入即可播放。在waveOutWrite之前需要使用调用waveOutPrepareHeader,播放完之后使用清楚waveOutUnPrepareHeader清楚waveOutPrepareHeader设置的东西。如果是把整个文件读入的方式播放的话了解和准备下上面描述的点即可实现。
接着我们采用流的方式播放,流的方式播放涉及到一个文件数据读取以及更换的过程,考虑到用户体验方面不至于出现声音咔咔的可以使用双缓冲来处理。双缓冲的使用:waveOut的双缓冲是使用两个WAVEHDR来实现。具体是使用waveOutOpen中的回调函数进行数据的加载,利用waveOutOpen函数可以捕捉到播放的开始结束以及关闭。必须要注意waveOutOpen的回调函数中不要使用和waveOut有关的系统api。所以回调函数用来进行数据的加载并不包括数据的播放,同时创建一个线程用于播放即可。上面是播放的大体过程,下面给出流播放的部分代码供参考。
class CWaveOut
{
public:
CWaveOut(const char* const pFileName);
~CWaveOut(void);
int Play();
private:
//加载音频的格式
int LoadAudioFormat(void);
//为两个缓冲区分配空间
int GetMemForBlock(void);
//缓冲区初始化
void WriteAudioBlock(HWAVEOUT hWaveOut);
//播放线程
static DWORD _stdcall PlayThread(LPVOID lpParam);
//回调函数
static void CALLBACK WaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);
HWAVEOUT m_hWaveOut;
//自己设计的一个用于文件读取的类
CWavInfo *m_pWavInfo;
//文件是否结束
volatile int m_nIsEnd;
//两个WAVEHDR
volatile int m_nReadyOne; //是否准备好
char *m_pBlockOne; //数据区
WAVEHDR m_stHeaderOne; //第一个wavehdr
volatile int m_nReadTwo;
char *m_pBlockTwo;
WAVEHDR m_stHeaderTwo;
unsigned int m_nSize;
char m_sFileName[255];
WAVEFORMATEX m_stWfx;
};
上面是类的定义,关于waveOutOpen函数的使用,想说下其回调函数,设置之后在调用waveOutOpen,打开、播放结束、关闭的时候都会给回调函数发送一个消息,对应的消息为WOM_OPEN、WOM_DONE、WOM_CLOSE。正如上面所说的那样,要捕捉播放结束WOM_DONE这个消息进行数据的填充。下面是播放函数以及回调函数的代码描述:
int CWaveOut::Play(void)
{
HANDLE pHandle;
if(LoadAudioFormat() != 0)
{
fprintf(stderr, "unable to loadformat data\n");
return -1;
}
//设置回调函数
if(waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &m_stWfx, (DWORD)WaveOutProc, (DWORD)this, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
{
fprintf(stderr, "unable to openWAVE_MAPPER device\n");
return -1;
}
m_nIsEnd = 0;
m_nReadyOne = 0;
m_nReadTwo = 0;
//创建播放线程
pHandle = CreateThread(NULL, 0, PlayThread, this, 0, NULL);
if(pHandle == NULL)
{
printf("create thread error.\n");
return -2;
}
//初始化缓冲区数据
WriteAudioBlock(m_hWaveOut);
DWORD result;
MSG msg;
while(true)
{
//等待子线程结束返回,并处理消息和信号避免界面死掉
result = MsgWaitForMultipleObjects(1, &pHandle, false, INFINITE, QS_ALLINPUT);
if (result == (WAIT_OBJECT_0))
{
break;
}
else
{
PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
DispatchMessage(&msg);
}
}
waveOutClose(m_hWaveOut);
return 0;
}
回调函数这里处理的不是很好,只是用来实验,有兴趣的朋友可以参考并改善,最后应该不会用waveout的,一般选择dsound或者openal。嗯嗯,这周末我会把dsound、openal这两个的流音频播放一一补上的,木有时间。额~~
void CALLBACK CWaveOut::WaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
CWaveOut *pWaveOut = (CWaveOut *)dwInstance;
LPWAVEHDR pWaveHeader = (LPWAVEHDR)dwParam1;
switch(uMsg)
{
case WOM_DONE :
//拷贝数据
if((pWaveHeader == &(pWaveOut->m_stHeaderOne)) && (pWaveOut->m_nIsEnd == 0))
{
if(pWaveOut->m_pWavInfo->ReadABlockData(pWaveOut->m_pBlockOne, &(pWaveOut->m_nSize)) != 0)
{
pWaveOut->m_nIsEnd = 1;
return;
}
pWaveOut->m_nReadyOne = 1;
}
else if((pWaveHeader == &(pWaveOut->m_stHeaderTwo)) && (pWaveOut->m_nIsEnd == 0))
{
if(pWaveOut->m_pWavInfo->ReadABlockData(pWaveOut->m_pBlockTwo, &(pWaveOut->m_nSize)) != 0)
{
pWaveOut->m_nIsEnd = 1;
return;
}
pWaveOut->m_nReadTwo = 1;
}
break;
}
return;
}
最后就只差播放线程了。
DWORD _stdcall CWaveOut::PlayThread(LPVOID lpParam)
{
//得到线程的传入参数,转化成类指针,这样就可以在线程中使用类的数据以及函数等
CWaveOut *pWaveOut = (CWaveOut *)lpParam;
while(true)
{
if(pWaveOut->m_nReadyOne == 1)
{
memset(&(pWaveOut->m_stHeaderOne), 0, sizeof(WAVEHDR));
pWaveOut->m_stHeaderOne.dwBufferLength = pWaveOut->m_nSize;
pWaveOut->m_stHeaderOne.lpData = pWaveOut->m_pBlockOne;
//这里和下面都是一样的用于向设备写数据也就是播放
waveOutPrepareHeader(pWaveOut->m_hWaveOut, &(pWaveOut->m_stHeaderOne), sizeof(WAVEHDR));
waveOutWrite(pWaveOut->m_hWaveOut, &(pWaveOut->m_stHeaderOne), sizeof(WAVEHDR));
waveOutUnprepareHeader(pWaveOut->m_hWaveOut, &(pWaveOut->m_stHeaderOne), sizeof(WAVEHDR));
pWaveOut->m_nReadyOne = 0;
continue;
}
else if(pWaveOut->m_nReadTwo == 1)
{
memset(&(pWaveOut->m_stHeaderTwo), 0, sizeof(WAVEHDR));
pWaveOut->m_stHeaderTwo.dwBufferLength = pWaveOut->m_nSize;
pWaveOut->m_stHeaderTwo.lpData = pWaveOut->m_pBlockTwo;
waveOutPrepareHeader(pWaveOut->m_hWaveOut, &(pWaveOut->m_stHeaderTwo), sizeof(WAVEHDR));
waveOutWrite(pWaveOut->m_hWaveOut, &(pWaveOut->m_stHeaderTwo), sizeof(WAVEHDR));
waveOutUnprepareHeader(pWaveOut->m_hWaveOut, &(pWaveOut->m_stHeaderTwo), sizeof(WAVEHDR));
pWaveOut->m_nReadTwo = 0;
continue;
}
if(pWaveOut->m_nIsEnd == 1)
{
break;
}
}
return 0;
}
这就完成了一个waveout的流播放,后续我会补上dsound、openal的流播放介绍以及最终的demo,这个demo还会加上mp3的播放,当然解码是使用libmad开源的解码库。
边学习边总结,里面肯定有一下术语或者描述有错,有任何错误请一定要指出!谢谢~~
每天学一点生活更精彩~洗洗睡明早上班!