使用WaveOut API播放WAV音频文件(解决卡顿)
虽然waveout已经过时,但是其api简单,有些时候也还是需要用到。
其实还是自己上msdn查阅相应api最靠谱,waveout也有提供暂停、设置音量等接口的,这里给个链接,需要的可以自己查找:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd743834(v=vs.85).aspx
waveout播放音频流程:
- 初始化设备并获得句柄,调用waveOutOpen
- 初始化WAVEHDR结构体,包含了要播放的音频数据,调用waveOutPrepareHeader
- 播放WAVEHDR指定的数据,调用waveOutWrite
- 播放结束,调用waveOutClose
函数介绍:
1.waveOutOpen,初始化waveout,指定音频的格式、回调方式等
MMRESULT waveOutOpen( LPHWAVEOUT phwo, UINT_PTR uDeviceID, LPWAVEFORMATEX pwfx, DWORD_PTR dwCallback, DWORD_PTR dwCallbackInstance, DWORD fdwOpen );
phwo是返回的waveOut的句柄,后面的waveOutWrite等函数都需要传入该句柄。
uDeviceID是播放设备的ID,不知道是什么就填WAVE_MAPPER,系统自动选择。
pwfx传入一个WAVEFORMATEX结构体,该结构体包含了待播放音频的格式(采样率、声道数等)。
dwCallback表示回调方式,因为在调用waveOutWrite之后,系统会在另外一个线程中播放所传入的音频数据,当系统播放完这一段音频后会通过回调的方式来通知我们进行下一步操作。该值可以是:一个waveOutProc回调函数;一个窗口的句柄;一个线程的ID;一个EVENT句柄;NULL。本次我使用的EVENT句柄,网上的代码都是使用回调函数的方式,其实使用EVENT要更方便。
dwCallbackInstance用于在回调之间传递数据,比如说向回调函数传递waveOut的句柄。
fdwOpen标志,一般用于表示dwCallback的类型,比如CALLBACK_FUNCTION表示dwCallback是回调函数、CALLBACK_EVENT表示dwCallback是EVEN句柄。
返回值:返回MMSYSERR_NOERROR表示成功,其他表示失败。
2.waveOutPrepareHeader,初始化一个WAVEHDR结构体
MMRESULT waveOutPrepareHeader( HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh );
hwo为waveOutOpen返回的的句柄。
pwh传入一个WAVEHDR结构体,包括了要播放的音频数据以及相应的一些信息。在调用该函数之前需要设置dwFlags(填0),dwBufferLength(待播放音频数据的长度),lpData(待播放的音频数据)这三个字段。需要注意的是:要确保系统在播放这一段音频的过程中该结构体有效并且不要有改动;音频数据的缓存由自己申请,并且在调用播放函数后系统不会对其进行拷贝,所以在此过程中也不要对该缓存进行改动;在释放lpData的内存前需要调用waveOutUnprepareHeader。
cbwh填sizeof(WAVEHDR)即可。
3.waveOutWrite,播放WAVEHDR中指定的音频数据
MMRESULT waveOutWrite( HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh );
hwo为waveOutOpen返回的的句柄。
pwh传入使用waveOutPrepareHeader初始化过的WAVEHEDR结构体。
cbwh填sizeof(WAVEHDR)即可。
播放的同步:
由于系统播放时是在另一个线程中执行,所以需要用到线程同步相关知识,上面所提到的“回调”就是解决这个问题。本次使用的是EVENT。
EVENT有两种状态:激活、未激活。调用WaitForSingleObject(event, INFINITE)会阻塞进程直至event变为激活状态;调用SetEvent(event)可设置event为激活状态;调用ResetEvent(event)可设置event为未激活状态。当waveout播放完成之后,系统会将我们在waveOutOpen中指定的EVENT置为激活状态。
了解event的机制之后,我们可以这样:①设置event为未激活状态;②调用waveOutWrite播放指定音频;③调用WaitForSingleObject等待event被激活(等待播放完成);④回到第“②”步,如此循环。
避免卡顿:
在播放的时候有很重要的一点:播放的缓存至少需要两个。因为在调用waveOutWirite后系统内核会将其加入“播放队列”,与此同时,还有一个播放线程依次从该队列取出数据并播放,并且每播放完一个节点就会调用上面所说的“回调”。只要保持“播放队列”里面至少有两个节点就不会造成卡顿。
因此,我们只需要在开始播放时调用两次waveOutWrite,然后在“回调”中调用一次waveOutWrite。这样也就保持“播放队列”中(几乎)始终会有两个节点。
为此,我设计了一个类WaveOut,下面是完整代码:
1 #include <iostream> 2 #include <fstream> 3 #include "waveout.h" 4 5 int main(int argc, char *argv[]) 6 { 7 char buffer[1000 * 8]; 8 int nRead; 9 unsigned short channels = 1; 10 unsigned long sampleRate = 8000; 11 unsigned short bitsPerSample = 16; 12 std::ifstream ifile("D:\\record\\blow.wav", std::ifstream::binary); 13 WaveOut wvOut; 14 15 if (!ifile) 16 { 17 std::cout << "failed to open file.\n"; 18 return 0; 19 } 20 21 ifile.seekg(22); 22 ifile.read((char*)&channels, 2); 23 ifile.seekg(24); 24 ifile.read((char*)&sampleRate, 4); 25 ifile.seekg(34); 26 ifile.read((char*)&bitsPerSample, 2); 27 ifile.seekg(44); 28 29 std::cout << "sample rate: " << sampleRate 30 << ", channels: " << channels 31 << ", bits per sample: " << bitsPerSample << std::endl; 32 33 if (wvOut.open(sampleRate, bitsPerSample, channels) < 0) 34 { 35 std::cout << "waveout open failed.\n"; 36 return 0; 37 } 38 39 while (ifile.read(buffer, sizeof(buffer))) 40 { 41 nRead = ifile.gcount(); 42 // std::cout << "read " << nRead << " bytes.\n"; 43 if (wvOut.push(buffer, nRead) < 0) 44 std::cout << "play failed.\n"; 45 } 46 if (wvOut.flush() < 0) 47 std::cout << "flush failed\n"; 48 std::cout << "play done.\n"; 49 50 system("pause"); 51 return 0; 52 }main.cpp
原创文章,转载请注明。
- Wave 文件(9): 使用 waveOut... 函数播放 wav 文件
- Wave 文件(12): 使用 waveOut...重复播放 wav 文件
- 操作 Wave 文件(12): 使用 waveOut...重复播放 wav 文件
- 操作 Wave 文件(9): 使用 waveOut... 函数播放 wav 文件
- HTML5 API---使用WebAudio API播放音频文件
- window API播放pcm格式音频文件,函数waveOutOpen等
- android使用mount命令挂载远程视频文件大于10G,播放时出现卡顿的解决办法
- 一个使用WINDOWS媒体库的WAV音频文件播放的示例
- C#使用SoundPlayer播放WAV音频文件
- 全双工音频播放器在c#中使用waveIn / waveOut api
- 用 Qt 的 QAudioOutput 类播放 WAV 音频文件(使用了libsndfile外部库)
- Ubuntu7.04使用totem-xine,安装libxine1-ffmpeg后,rm文件播放无声问题的解决办法
- C#中用API实现MP3等音频文件的播放类
- VC中使用低级音频函数WaveX播放声音文件
- Ubuntu7.04使用totem-xine,安装libxine1-ffmpeg后,rm文件播放无声问题的解决办法
- Ubuntu7.04使用totem-xine,安装libxine1-ffmpeg后,rm文件播放无声问题的解决办法
- Ubuntu7.04使用totem-xine,安装libxine1-ffmpeg后,rm文件播放无声问题的解决办法
- 用API函数播放wav文件声音不连续的解决方法
- Ubuntu7.04使用totem-xine,安装libxine1-ffmpeg后,rm文件播放无声问题的解决办法
- 在一个win Forms应用中嵌入以及播放WAV音频文件(翻译五)