使用WaveOut接口播放WAV文件

时间:2022-11-28 19:44:31

我需要在插件中实现一边接收数据,一边展示媒体文件的功能,因此从最简单的音频文件来开始研究,绝大多数API如:playSound等都是根据完整文件来播放音频的,即便是内存模式也是需要将整个文件全部读入一片内容,然后将内存指针传递给playSound。最后逐渐寻找到WaveOut接口。

要一边接收数据一边播放,首先需要得到头信息,然后使用多个缓存在WaveOutWrite中交替为音频设备填充数据。

这里有几个我觉得不错的参考资料:

http://apps.hi.baidu.com/share/detail/59300070

http://blog.csdn.net/jinlking/article/details/3864259

我在做的过程中,遇到的比较棘手的问题是:1:在WaveOutOpen函数中的回调函数的处理。2、多个缓存交替播放时声音一卡一卡的。

第一个问题:因为我使用了一个WavePlayer类来实现我的功能,在这个类的构造函数中做了一些准备工作,根据文件头数据音乐信息。然后,最后play函数中指定回调函数,准备最开始的数据填充接受三个参数:最后每当播放完毕就会调用回调函数,在回调函数中填充当前数据。

回调函数必须是全局函数,在类中只能以static函数存在,但是静态成员函数无法访问类的非静态成员变量,因此这里很久才搞定,最后解决方案是在WaveOutOpen中奖第五个参数设置为this。利用this指针来将对象传递到静态回调函数中,在回调函数中将这个指针强制转换成wavePlayer*。这样就可以访问该对象的非静态成员了。

bool WavePlayer::wavePlay(HPSTR wavData,long dataSize,int numOfBuf/* =1 */)
{
        //多缓存交替播放
	DWORD rs=0;
	//检查音频设备,返回音频输出设备的性能
	if (MMSYSERR_NOERROR != (rs = waveOutGetDevCaps(WAVE_MAPPER, &pwoc, sizeof(WAVEOUTCAPS))))
	{
		ErrHandler(_T("waveOutGetDevCaps()"),rs);
		return false;
	}

	if (MMSYSERR_NOERROR != (rs =waveOutOpen(&hWaveOut, WAVE_MAPPER, &wvformat, (DWORD)waveOutCallBack, (DWORD_PTR)this, CALLBACK_FUNCTION)))
	{
		ErrHandler(_T("waveOutOpen()"),rs);
		return false;
	}
	HPSTR dataBlocks=new char[dataSize*numOfBuf];
	memcpy(dataBlocks,wavData,dataSize*numOfBuf);
	lpWavhdr = new WAVEHDR[numOfBuf];
	for (int i=0;i<numOfBuf;++i)
	{
		//准备播放数据
		lpWavhdr[i].lpData = (HPSTR)(dataBlocks+i*dataSize);
		lpWavhdr[i].dwBufferLength = dataSize;
		lpWavhdr[i].dwFlags = 0;
		if (MMSYSERR_NOERROR != (rs =waveOutPrepareHeader(hWaveOut, (LPWAVEHDR)(&lpWavhdr[i]), sizeof(WAVEHDR))))
		{
			ErrHandler(_T("waveOutPrepareHeader()"),rs);
			return false;
		}
		//将数据写入设备并开始播放
		if (MMSYSERR_NOERROR != (rs =waveOutWrite(hWaveOut,(LPWAVEHDR)(&lpWavhdr[i]) , sizeof(WAVEHDR))))
		{
			ErrHandler(_T("waveOutWrite()"),rs);
			return false;
		}
	}
	*pIsLoop=true;
	*pIsBufNull=true;
	lpData=new char[dataSize];
	return true;
}
回调函数:

void CALLBACK WavePlayer::waveOutCallBack(HWAVEOUT hwo, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
	WavePlayer* pPlayer=(WavePlayer*)dwInstance;
	
	if(uMsg == WOM_DONE)
	{
		if (!*(pPlayer->pIsLoop))
		{
			return;//不在交替播放循环,直接返回
		}

		if (*(pPlayer->pIsBufNull)&&!*(pPlayer->pIsEod))
		{
			return;//缓存为空,但不是文件尾部,返回
		}
		
		LPWAVEHDR pWaveHeader = (LPWAVEHDR)dwParam1;//系统自动识别是哪一个WAVEHDR播放完毕
		pWaveHeader->dwFlags=0;
		waveOutUnprepareHeader( hwo, pWaveHeader, sizeof(WAVEHDR) );//播放完后须调用此函数

		if (*(pPlayer->pIsEod)&&*(pPlayer->pIsBufNull))
		{
			return;//数据已经读取完毕,且缓存为空。Unprepare之后再返回
		}
		//此处填充WAVEHDR的lpdate缓冲
		pWaveHeader->lpData=pPlayer->lpData;
		pWaveHeader->dwBufferLength=pPlayer->mBufSize;
		pWaveHeader->dwFlags=0;

		waveOutPrepareHeader( hwo, pWaveHeader, sizeof(WAVEHDR));
		waveOutWrite( hwo, pWaveHeader, sizeof(WAVEHDR) );
		*(pPlayer->pIsBufNull)=true;
	}
	return;
}
实际接收数据是在main中进行的。实现得比较别扭,用了一个while(1):

	/************************************************************************/
	/* 双缓存交替播放                                                        */
	/************************************************************************/
	long bufSize=8*1024;
	int bufNum=2;
	long curPos=44+bufSize*bufNum;
	HPSTR lpPreData=new char[bufSize*bufNum];
	SetFilePointer(hFile,44,NULL,FILE_BEGIN);
	ReadFile(hFile,lpPreData,bufSize*bufNum,&dwRead,NULL);
	*wavPlayer.pIsBufNull=false;
	wavPlayer.mBufSize=bufSize;

	while(1)
 	{
		if (!*(wavPlayer.pIsLoop))
		{
			wavPlayer.wavePlay(lpPreData,bufSize,bufNum);
		}
		if (*wavPlayer.pIsLoop)
		{
			if (lpPreData!=NULL)
			{
				delete[] lpPreData;
				lpPreData=NULL;
			}
		}

		if (*wavPlayer.pIsBufNull && !(*wavPlayer.pIsEod))
		{
			SetFilePointer(hFile,curPos,NULL,FILE_BEGIN);
			ReadFile(hFile,wavPlayer.lpData,bufSize,&dwRead,NULL);
			if (0==dwRead||ERROR_HANDLE_EOF==GetLastError())
			{
				*wavPlayer.pIsEod=true;
				*wavPlayer.pIsBufNull=false;
				break;
			}
			curPos+=bufSize;
			*wavPlayer.pIsBufNull=false;
		}
		Sleep(bufSize/0xb110);
 	}
获取数据这里就是readfile来实现的,最终将会使用OnStreamDataArrived接收数据。

第二个问题比较小:就是声音卡顿的现象。但是找了很久才找到;就是在回调函数中填充数据的时候处理得不合理:

pWaveHeader->lpData=pPlayer->lpData;
上面这句改成

memcpy(pWaveHeader->lpData,pPlayer->lpData,pPlayer->mBufSize);
就不卡了。但是还需要在while中的sleep时间做一下调整,否则可能在不同的电脑上会出现不同的效果。

关于实现双缓冲连续播放大文件的最好参考资料是前面给出的第一个链接。不过我是在作完这些之后才看到。否则我的程序应该会更漂亮一点。