使用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

 

    原創文章,轉載請注明。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM