雖然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 }
主函數,從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 }
原創文章,轉載請注明。