关于用低级API函数播放wav文件声音不连续的解决方法
2007-05-14 16:54
621 查看
作为一个多媒体技术方面的初学者,我从wav文件的播放开始了解媒体播放的流程。
于是从建立两个线程开始,线程1用来将文件中的数据读到Buffer中去,以后称为读线程,线程2用来将Buffer中的数据送到设备的缓存,以后成为写线程。在最开始的时候,本着从易到难的精神,只开了一个buffer,我的想法很简单,先将数据写进这个Buffer,然后再送入设备进行播放,结果是每个Buffer中间有个空隙,这件事情是明摆着的,只采用一个Buffer肯定会导致这样的结果。在我看来是因为,设备将缓存中的数据播放完以后回头去Buffer取数据时发现Buffer是空的,因而再去读线程将PCM数据写入Buffer,也就是说设备有一段时间是无事可做的,听起来当然是断断续续的了。
听了高手的意见建立了一个包含两个Buffer的队列。
在用缓冲队列播放wav文件的时候却始终遇到一个问题,在播放的时候会在每个buffer读取数据的空隙产生停顿,这不是还和一个Buffer一样?难道采用两个Buffer也不行,要更多的Buffer?在网上查阅一些资料时发现理解上的偏差,两个buffer的切换流程如下所示:
Buffer[0] read;
Buffer[1] read;
Buffer[0] write to device;
Buffer[0]read;
Buffer[1]write to device;
也就是说,在Buffer[0]写入设备缓存之后就立刻再去取数据,也就是要保证在每个时刻都至少有一个Buffer里面有数据。
而我没有预先将Buffer写满,结局自然是和一个Buffer一样,想到这里我信心满满,觉得问题就要解决了,实际上还有很多问题。最终我放弃了建立两个子线程的想法(实际也不需要)。
有关Buffer的切换是想明白了,具体实施起来有两种方法。两种方法本质上是相同的,只不过是在waveOutOpen的时候最后一个参数的设置不同。
方法一:使用CALLBACK_FUNCTION(回调函数)。详见wince help中关于WaveOutProc函数的说明,只需要一个线程,通过回调函数查看系统消息WOM_DONE,来确定Buffer中的数据有没有被播放完毕,如果播放完毕则首先清空设备缓存(waveOutUnprepare),然后写Buffer,将Buffer中的数据送入设备缓存。还要判断文件是否已经读完,如果读完则跳出CallBack函数。
方法二:使用CALLBACK_EVENT。另外建立一个子线程。微软方面提供的文档告诉我们,应该检查dwFlags&&WHDR_DONE 是否为WHDR_DONE,以确保Buffer中的数据已经被播放完毕。当子线程开始执行时要判断这两个参数为真时,才能清空设备缓存。
两者的不同是,方法一中,系统会自动将播放完毕的Buffer地址返回到程序中,当callback函数被调用时可以直接使用;使用方法二,则要不断地判断Buffer中的数据是否已经播放完毕。
ps:采用多少个只要每次填入的数据(播放时间)在1/50~1/70秒以上,就不会出现咔咔的声音,和具体和设备性能以及声卡驱动有关。
方法一:callback_function
void CALLBACK waveOutProc(HWAVEOUT hWaveOut,UINT uMsg,DWORD dwInstance,
DWORD dwParam1,DWORD dwParam2)
...{
WAVEHDR* pHdr = (PWAVEHDR)dwParam1; // 该参数由系统自动返回
DWORD dwBytesRead;
if(uMsg != WOM_DONE ) //判断是否播放完
...{
printf("Error Wom_Done ");
return;
}
if(g_dwBytesleft == 0)
...{
g_res0 = waveOutReset(g_hWaveOut);
g_res0 = waveOutClose(g_hWaveOut);
if(g_res0 != MMSYSERR_NOERROR)
printf("Wave out close error ");
GetExitCodeProcess( hProcess, lpExitCode);
CloseHandle(hProcess);
return;
} //如果文件读完,退出程序
mr = waveOutUnprepareHeader(hWaveOut,pHdr,sizeof(WAVEHDR));//清空设备缓存
//判断是否成功
DWORD dwBytesRequire = g_dwBytesleft < BUFFER_LENGTH ? g_dwBytesleft:BUFFER_LENGTH; //考虑剩余数据不足Buffer长度的情况
ReadFile(g_fh, pHdr->lpData, dwBytesRequire ,&dwBytesRead, NULL);
//判断是否成功
g_dwBytesleft -= dwBytesRead;
mr = waveOutPrepareHeader(hWaveOut,pHdr,sizeof(WAVEHDR));
//判断是否成功
g_res0= waveOutWrite(g_hWaveOut, pHdr, sizeof(WAVEHDR));
//判断是否成功
}
方法二:callback_event
DWORD WaveThreadFunc(DATABUFFER* pBuffer)
...{
printf("start thread ");
DWORD dwBytesRead = NULL;
g_bEndThread = FALSE;
int i = 0;
while(!g_bEndThread)
...{
DWORD dw = WaitForSingleObject(g_hWaveEvent,0); //等待事件信号
if(dw == WAIT_OBJECT_0)
...{
printf("start switch ");
...{
pBuffer = &g_WaveBuffers[i]; //依次取各个Buffer
if((pBuffer->Hdr.dwFlags && WHDR_DONE) == WHDR_DONE)
//Buffer内数据已经播放完成
...{
waveOutUnprepareHeader(g_hWaveOut,&pBuffer->Hdr,sizeof(WAVEHDR));
DWORD dwBytesRequire = g_dwBytesleft < BUFFERSIZE ? g_dwBytesleft:BUFFERSIZE;
ReadFile(g_fh, pBuffer->Hdr.lpData, dwBytesRequire ,&dwBytesRead, NULL);
g_dwBytesleft -= dwBytesRead;
waveOutPrepareHeader(g_hWaveOut,&pBuffer->Hdr,sizeof(WAVEHDR));
waveOutWrite(g_hWaveOut, &pBuffer->Hdr, sizeof(WAVEHDR));
}
}
i++;
if(i == NBUFFERS)
i = i%NBUFFERS;
ResetEvent(g_hWaveEvent);
}
}
ExitThread (0);
return 0;
}
于是从建立两个线程开始,线程1用来将文件中的数据读到Buffer中去,以后称为读线程,线程2用来将Buffer中的数据送到设备的缓存,以后成为写线程。在最开始的时候,本着从易到难的精神,只开了一个buffer,我的想法很简单,先将数据写进这个Buffer,然后再送入设备进行播放,结果是每个Buffer中间有个空隙,这件事情是明摆着的,只采用一个Buffer肯定会导致这样的结果。在我看来是因为,设备将缓存中的数据播放完以后回头去Buffer取数据时发现Buffer是空的,因而再去读线程将PCM数据写入Buffer,也就是说设备有一段时间是无事可做的,听起来当然是断断续续的了。
听了高手的意见建立了一个包含两个Buffer的队列。
在用缓冲队列播放wav文件的时候却始终遇到一个问题,在播放的时候会在每个buffer读取数据的空隙产生停顿,这不是还和一个Buffer一样?难道采用两个Buffer也不行,要更多的Buffer?在网上查阅一些资料时发现理解上的偏差,两个buffer的切换流程如下所示:
Buffer[0] read;
Buffer[1] read;
Buffer[0] write to device;
Buffer[0]read;
Buffer[1]write to device;
也就是说,在Buffer[0]写入设备缓存之后就立刻再去取数据,也就是要保证在每个时刻都至少有一个Buffer里面有数据。
而我没有预先将Buffer写满,结局自然是和一个Buffer一样,想到这里我信心满满,觉得问题就要解决了,实际上还有很多问题。最终我放弃了建立两个子线程的想法(实际也不需要)。
有关Buffer的切换是想明白了,具体实施起来有两种方法。两种方法本质上是相同的,只不过是在waveOutOpen的时候最后一个参数的设置不同。
方法一:使用CALLBACK_FUNCTION(回调函数)。详见wince help中关于WaveOutProc函数的说明,只需要一个线程,通过回调函数查看系统消息WOM_DONE,来确定Buffer中的数据有没有被播放完毕,如果播放完毕则首先清空设备缓存(waveOutUnprepare),然后写Buffer,将Buffer中的数据送入设备缓存。还要判断文件是否已经读完,如果读完则跳出CallBack函数。
方法二:使用CALLBACK_EVENT。另外建立一个子线程。微软方面提供的文档告诉我们,应该检查dwFlags&&WHDR_DONE 是否为WHDR_DONE,以确保Buffer中的数据已经被播放完毕。当子线程开始执行时要判断这两个参数为真时,才能清空设备缓存。
两者的不同是,方法一中,系统会自动将播放完毕的Buffer地址返回到程序中,当callback函数被调用时可以直接使用;使用方法二,则要不断地判断Buffer中的数据是否已经播放完毕。
ps:采用多少个只要每次填入的数据(播放时间)在1/50~1/70秒以上,就不会出现咔咔的声音,和具体和设备性能以及声卡驱动有关。
方法一:callback_function
void CALLBACK waveOutProc(HWAVEOUT hWaveOut,UINT uMsg,DWORD dwInstance,
DWORD dwParam1,DWORD dwParam2)
...{
WAVEHDR* pHdr = (PWAVEHDR)dwParam1; // 该参数由系统自动返回
DWORD dwBytesRead;
if(uMsg != WOM_DONE ) //判断是否播放完
...{
printf("Error Wom_Done ");
return;
}
if(g_dwBytesleft == 0)
...{
g_res0 = waveOutReset(g_hWaveOut);
g_res0 = waveOutClose(g_hWaveOut);
if(g_res0 != MMSYSERR_NOERROR)
printf("Wave out close error ");
GetExitCodeProcess( hProcess, lpExitCode);
CloseHandle(hProcess);
return;
} //如果文件读完,退出程序
mr = waveOutUnprepareHeader(hWaveOut,pHdr,sizeof(WAVEHDR));//清空设备缓存
//判断是否成功
DWORD dwBytesRequire = g_dwBytesleft < BUFFER_LENGTH ? g_dwBytesleft:BUFFER_LENGTH; //考虑剩余数据不足Buffer长度的情况
ReadFile(g_fh, pHdr->lpData, dwBytesRequire ,&dwBytesRead, NULL);
//判断是否成功
g_dwBytesleft -= dwBytesRead;
mr = waveOutPrepareHeader(hWaveOut,pHdr,sizeof(WAVEHDR));
//判断是否成功
g_res0= waveOutWrite(g_hWaveOut, pHdr, sizeof(WAVEHDR));
//判断是否成功
}
方法二:callback_event
DWORD WaveThreadFunc(DATABUFFER* pBuffer)
...{
printf("start thread ");
DWORD dwBytesRead = NULL;
g_bEndThread = FALSE;
int i = 0;
while(!g_bEndThread)
...{
DWORD dw = WaitForSingleObject(g_hWaveEvent,0); //等待事件信号
if(dw == WAIT_OBJECT_0)
...{
printf("start switch ");
...{
pBuffer = &g_WaveBuffers[i]; //依次取各个Buffer
if((pBuffer->Hdr.dwFlags && WHDR_DONE) == WHDR_DONE)
//Buffer内数据已经播放完成
...{
waveOutUnprepareHeader(g_hWaveOut,&pBuffer->Hdr,sizeof(WAVEHDR));
DWORD dwBytesRequire = g_dwBytesleft < BUFFERSIZE ? g_dwBytesleft:BUFFERSIZE;
ReadFile(g_fh, pBuffer->Hdr.lpData, dwBytesRequire ,&dwBytesRead, NULL);
g_dwBytesleft -= dwBytesRead;
waveOutPrepareHeader(g_hWaveOut,&pBuffer->Hdr,sizeof(WAVEHDR));
waveOutWrite(g_hWaveOut, &pBuffer->Hdr, sizeof(WAVEHDR));
}
}
i++;
if(i == NBUFFERS)
i = i%NBUFFERS;
ResetEvent(g_hWaveEvent);
}
}
ExitThread (0);
return 0;
}
相关文章推荐
- 用API函数播放wav文件声音不连续的解决方法
- “在win7下用WebEx 播放器播放 *.wrf格式文件耳机没有声音” 解决方法
- WinCE系统播放wav声音文件的实现方法
- 关于CI框架通过修改.htaccess文件的办法解决无法加载资源文件的方法
- 关于MMC不能打开文件的解决方法
- Smplayer播放wmv和avi的文件有箭头的问题解决方法
- 关于SAS不能读取Excel2007文件的原因及解决方法
- 关于/dev/null 和 /dev/zero文件详解以及误删/dev/null和/dev/zero的解决方法和利用/dev/zero进行磁盘IO测试方法
- 关于未能加载文件或程序集 system.web.extensions解决方法
- 关于vc++6.0“打开文件“功能问题的解决方法
- 关于Can not update .ICEauthority文件解决方法
- ASP.NET中将声音文件添加到资源中并进行播放的方法
- 使用DirectX播放wav声音文件
- 关于Sql Server企业管理器MMC 不能打开文件的解决方法
- 关于AndroidStudio R文件莫名其妙缺失的快速解决方法
- iOS真机播放MP4视频文件不出来的解决方法 AVPlayer
- MFC中使用SDL播放音频没有声音的解决方法
- VC中使用低级音频函数WaveX播放声音文件
- 关于ubuntu无法播放土豆视频的解决方法
- 关于CppSqlite中数据库文件中文路径识别问题的解决方法