使用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 <windows.h>
  2 #pragma comment(lib, "winmm.lib")
  3 
  4 #define MAX_BUFFER_SIZE (1024 * 8 * 16)
  5 
  6 class WaveOut
  7 {
  8 private:
  9     HANDLE        hEventPlay;
 10     HWAVEOUT    hWaveOut;
 11     WAVEHDR        wvHeader[2];
 12     CHAR*        bufCaching;
 13     INT            bufUsed;
 14     INT            iCurPlaying; /* index of current playing in 'wvHeader'. */
 15     BOOL        hasBegan;
 16 public:
 17     WaveOut();
 18     ~WaveOut();
 19     int open(DWORD nSamplesPerSec, WORD wBitsPerSample, WORD nChannels);
 20     void close();
 21     int push(const CHAR* buf, int size); /* push buffer into 'bufCaching', if fulled, play it. */
 22     int flush(); /* play the buffer in 'bufCaching'. */
 23 private:
 24     int play(const CHAR* buf, int size);
 25 };
 26 
 27 WaveOut::WaveOut() : hWaveOut(NULL)
 28 {
 29     wvHeader[0].dwFlags = 0;
 30     wvHeader[1].dwFlags = 0;
 31     wvHeader[0].lpData = (CHAR*)malloc(MAX_BUFFER_SIZE);
 32     wvHeader[1].lpData = (CHAR*)malloc(MAX_BUFFER_SIZE);
 33     wvHeader[0].dwBufferLength = MAX_BUFFER_SIZE;
 34     wvHeader[1].dwBufferLength = MAX_BUFFER_SIZE;
 35 
 36     bufCaching = (CHAR*)malloc(MAX_BUFFER_SIZE);
 37     hEventPlay = CreateEvent(NULL, FALSE, FALSE, NULL);
 38 }
 39 WaveOut::~WaveOut()
 40 {
 41     close();
 42     free(wvHeader[0].lpData);
 43     free(wvHeader[1].lpData);
 44     free(bufCaching);
 45     CloseHandle(hEventPlay);
 46 }
 47 int WaveOut::open(DWORD nSamplesPerSec, WORD wBitsPerSample, WORD nChannels)
 48 {
 49     WAVEFORMATEX wfx;
 50 
 51     if (!bufCaching || !hEventPlay || !wvHeader[0].lpData || !wvHeader[1].lpData)
 52     {
 53         return -1;
 54     }
 55 
 56     wfx.wFormatTag = WAVE_FORMAT_PCM;
 57     wfx.nChannels = nChannels;
 58     wfx.nSamplesPerSec = nSamplesPerSec;
 59     wfx.wBitsPerSample = wBitsPerSample;
 60     wfx.cbSize = 0;
 61     wfx.nBlockAlign = wfx.wBitsPerSample * wfx.nChannels / 8;
 62     wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * wfx.wBitsPerSample / 8;
 63 
 64     /* queries the device if it supports the given format.*/
 65 //    if (waveOutOpen(NULL, 0, &wfx, NULL, NULL, WAVE_FORMAT_QUERY))
 66 //    {
 67 //        return -1;
 68 //    }
 69     /* 'waveOutOpen' will call 'SetEvent'. */
 70     if (waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, (DWORD_PTR)hEventPlay, 0, CALLBACK_EVENT))
 71     {
 72         return -1;
 73     }
 74 
 75     waveOutPrepareHeader(hWaveOut, &wvHeader[0], sizeof(WAVEHDR));
 76     waveOutPrepareHeader(hWaveOut, &wvHeader[1], sizeof(WAVEHDR));
 77 
 78     if (!(wvHeader[0].dwFlags & WHDR_PREPARED) || !(wvHeader[1].dwFlags & WHDR_PREPARED))
 79     {
 80         return -1;
 81     }
 82 
 83     bufUsed = 0;
 84     iCurPlaying = 0;
 85     hasBegan = 0;
 86 
 87     return 0;
 88 }
 89 void WaveOut::close()
 90 {
 91     waveOutUnprepareHeader(hWaveOut, &wvHeader[0], sizeof(WAVEHDR));
 92     waveOutUnprepareHeader(hWaveOut, &wvHeader[1], sizeof(WAVEHDR));
 93     waveOutClose(hWaveOut);
 94     hWaveOut = NULL;
 95 }
 96 int WaveOut::push(const CHAR* buf, int size)
 97 {
 98 again:
 99     if (bufUsed + size < MAX_BUFFER_SIZE)
100     {
101         memcpy(bufCaching + bufUsed, buf, size);
102         bufUsed += size;
103     }
104     else
105     {
106         memcpy(bufCaching + bufUsed, buf, MAX_BUFFER_SIZE - bufUsed);
107 
108         if (!hasBegan)
109         {
110             if (0 == iCurPlaying)
111             {
112                 memcpy(wvHeader[0].lpData, bufCaching, MAX_BUFFER_SIZE);
113                 iCurPlaying = 1;
114             }
115             else
116             {
117                 ResetEvent(hEventPlay);
118                 memcpy(wvHeader[1].lpData, bufCaching, MAX_BUFFER_SIZE);
119 
120                 waveOutWrite(hWaveOut, &wvHeader[0], sizeof(WAVEHDR));
121                 waveOutWrite(hWaveOut, &wvHeader[1], sizeof(WAVEHDR));
122 
123                 hasBegan = 1;
124                 iCurPlaying = 0;
125             }
126         }
127         else if (play(bufCaching, MAX_BUFFER_SIZE) < 0)
128         {
129             return -1;
130         }
131 
132         size -= MAX_BUFFER_SIZE - bufUsed;
133         buf += MAX_BUFFER_SIZE - bufUsed;
134         bufUsed = 0;
135 
136         if (size > 0) goto again;
137     }
138     return 0;
139 }
140 int WaveOut::flush()
141 {
142     if (bufUsed > 0 && play(bufCaching, bufUsed) < 0)
143     {
144         return -1;
145     }
146     return 0;
147 }
148 int WaveOut::play(const CHAR* buf, int size)
149 {
150     WaitForSingleObject(hEventPlay, INFINITE);
151 
152     wvHeader[iCurPlaying].dwBufferLength = size;
153     memcpy(wvHeader[iCurPlaying].lpData, buf, size);
154 
155     if (waveOutWrite(hWaveOut, &wvHeader[iCurPlaying], sizeof(WAVEHDR)))
156     {
157         SetEvent(hEventPlay);
158         return -1;
159     }
160     iCurPlaying = !iCurPlaying;
161 
162     return 0;
163 }
waveout.h

    主函数,从WAV文件读取其采样率、采样位深、声道数并播放该WAV:

 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

 

    原创文章,转载请注明。

posted @ 2017-11-04 00:37  nonikon  阅读(5952)  评论(3编辑  收藏  举报