使用流式缓冲区播放 Wave 文件






2.89/5 (6投票s)
2003年7月27日
3分钟阅读

82477

3446
一篇关于流式缓冲区的文章。
引言
此项目使用 DirectSound 播放 wave 文件,但使用单个静态缓冲区需要大量时间和 CPU 周期来将声音加载到缓冲区。但使用流式缓冲区是一种以块为单位复制数据并播放相同块的方法,还可以不时更新同一块。因此,这是一种播放大型声音文件的更好方法。
使用代码
此项目以一种简单的方式使用 DirectSound,并且对 wave 文件的大部分操作都在 wavefile
类中完成。它的最重要的特性是它使用了流式缓冲区。如果要播放大型 wave 文件,将其完全加载到缓冲区中将需要很长时间来初始化。一种更好的方法是及时地将其逐步加载到循环缓冲区中。我使用了在 mmsystem.h 中定义的 mmio
函数。在这个类中,解析 wave 头的简单方法是通过将 RIFF 块的类型设置为 mmioFOURCC('W','A','V','E')
并进入其中以找到其 FMT 块来实现。然后从这个 FMT 块中读取 wave 格式并存储在 WAVEFORMATEX
结构中。在此之后,我深入研究了数据块。数据块不是一次性读取的。这就是所有重要性的体现。使用 ServiceBuffer
来服务主缓冲区,以使其在大多数时间内保持满状态。这由定时器过程通过 AudioStream
成员函数 TimerCallback
调用。主缓冲区可以使用任何大小,但在播放一个完整的缓冲区长度时,必须多次进行服务。主要目的是保持这个缓冲区尽可能地满。
class Timer { public: Timer(); ~Timer(); BOOL Create (UINT nPeriod, UINT nRes, DWORD dwUser, int (pfnCallback)(DWORD)); public: static void CALLBACK TimeProc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2); int (*m_pfnCallback)(DWORD); DWORD m_dwUser; UINT m_nPeriod; UINT m_nRes; UINT m_nIDTimer; UINT UID; void TimerStop(void); }; // Save current time (for elapsed time calculation) // This is a part of play function that initialises the timer m_nTimeStarted = timeGetTime (); // Kick off timer to service buffer m_ptimer = new Timer (); if (m_ptimer) { m_ptimer-> Create (m_nBufService, m_nBufService, DWORD(this), TimerCallback); }
这个 TimerCallback
函数以指定的间隔调用服务缓冲区。在 15 毫秒的差异处有播放游标和写入游标。DirectSound 不允许您在该位置写入数据。所以最好维护您自己的变量来保存有关已播放和待播放的缓冲区内存的信息。
BOOl AudioStream::SereviceBuffer() { //not to disturb while being serviced if (InterlockedExchange (&lInService, TRUE) == FALSE) { . . . . . . . . . . . . . . . . DWORD dwFreeSpace = GetMaxWriteSize(); dwDataremaining=m_pwavefile-> GetNumbBytesRemaining(); //there will be three cases //i)enough data to fill free space; //ii)data to fill is less than buffer size; //iii)all data has been send to buffer and are being played. //Fill free space with silence data.Deal with them accordingly. Write wave data; . . . . . . . . . else fRtn=FALSE; } else { // Service routine reentered. Do nothing, just return fRtn = FALSE; } return (fRtn); } <![if !supportEmptyParas]> <![endif]>
我遇到的问题
在制作这个项目时,我遇到的主要问题是读取 wave 文件。将 PCMWAVEFORMAT
(16 字节) 结构的内容复制到 WAVEFORMATEX
(18 字节) 结构时,总是出现错误。最糟糕的事情是,我在另一个文件中编译了程序的这部分,并且 memcpy
工作得很好,但在这里却不行。所以我不得不为 WAVEFORMATEX
结构变量分配 18 个字节,然后逐个填充结构。
我注意到的另一个奇怪的现象是,当我将 dsound.h 头文件放在 mmsystem.h 之前时。所以,如果你的头文件出现语法错误,请尝试将 mmsystem.h 放在 dsound.h 之前。最初的想法来自于 MSDN 网站上关于 流式缓冲区 的教程。在那里,我无法使用 TIMERCALLBACK
,所以我使用了旧的 C 风格的函数传递给另一个函数。
历史
- 版本 1.0