用API函数播放wav文件声音不连续的解决方法
作為一個多媒體技術方面的初學者,我從wav文件的播放開始了解媒體播放的流程。
??? 于是從建立兩個線程開始,線程1用來將文件中的數據讀到Buffer中去,以后稱為讀線程,線程2用來將Buffer中的數據送到設備的緩存,以后成為寫線程。在最開始的時候,本著從易到難的精神,只開了一個buffer,我的想法很簡單,先將數據寫進這個Buffer,然后再送入設備進行播放,結果是每個Buffer中間有個空隙,這件事情是明擺著的,只采用一個Buffer肯定會導致這樣的結果。在我看來是因為,設備將緩存中的數據播放完以后回頭去Buffer取數據時發現Buffer是空的,因而再去讀線程將PCM數據寫入Buffer,也就是說設備有一段時間是無事可做的,聽起來當然是斷斷續續的了。
??? 聽了高手的意見建立了一個包含兩個Buffer的隊列。
??? 在用緩沖隊列播放wav文件的時候卻始終遇到一個問題,在播放的時候會在每個buffer讀取數據的空隙產生停頓,這不是還和一個Buffer一樣?難道采用兩個Buffer也不行,要更多的Buffer?在網上查閱一些資料時發現理解上的偏差,兩個buffer的切換流程如下所示:
Buffer[0] read;
Buffer[1] read;
Buffer[0] write to device;
Buffer[0]read;
Buffer[1]write to device;
也就是說,在Buffer[0]寫入設備緩存之后就立刻再去取數據,也就是要保證在每個時刻都至少有一個Buffer里面有數據。
????而我沒有預先將Buffer寫滿,結局自然是和一個Buffer一樣,想到這里我信心滿滿,覺得問題就要解決了,實際上還有很多問題。最終我放棄了建立兩個子線程的想法(實際也不需要)。
??? 有關Buffer的切換是想明白了,具體實施起來有兩種方法。兩種方法本質上是相同的,只不過是在waveOutOpen的時候最后一個參數的設置不同。
方法一:使用CALLBACK_FUNCTION(回調函數)。詳見wince help中關于WaveOutProc函數的說明,只需要一個線程,通過回調函數查看系統消息WOM_DONE,來確定Buffer中的數據有沒有被播放完畢,如果播放完畢則首先清空設備緩存(waveOutUnprepare),然后寫Buffer,將Buffer中的數據送入設備緩存。還要判斷文件是否已經讀完,如果讀完則跳出CallBack函數。
方法二:使用CALLBACK_EVENT。另外建立一個子線程。微軟方面提供的文檔告訴我們,應該檢查dwFlags&&WHDR_DONE 是否為WHDR_DONE,以確保Buffer中的數據已經被播放完畢。當子線程開始執行時要判斷這兩個參數為真時,才能清空設備緩存。
??? 兩者的不同是,方法一中,系統會自動將播放完畢的Buffer地址返回到程序中,當callback函數被調用時可以直接使用;使用方法二,則要不斷地判斷Buffer中的數據是否已經播放完畢。
ps:采用多少個只要每次填入的數據(播放時間)在1/50~1/70秒以上,就不會出現咔咔的聲音,和具體和設備性能以及聲卡驅動有關。
?
?1方法一:callback_function?2void?CALLBACK?waveOutProc(HWAVEOUT?hWaveOut,UINT?uMsg,DWORD?dwInstance,
?3???????????DWORD?dwParam1,DWORD?dwParam2)
?4{
?5????WAVEHDR*?pHdr?=?(PWAVEHDR)dwParam1;?//?該參數由系統自動返回
?6????DWORD?dwBytesRead;
?7????if(uMsg?!=?WOM_DONE?)???//判斷是否播放完
?8????{
?9????????printf("Error?Wom_Done?");
10????????return;
11????}
12????if(g_dwBytesleft?==?0)
13???????????????????{????
14????????????????????????g_res0?=?waveOutReset(g_hWaveOut);
15????????????????????????g_res0?=?waveOutClose(g_hWaveOut);
16????????????????????????if(g_res0?!=?MMSYSERR_NOERROR)
17????????????????????????????printf("Wave?out?close?error??");
18????????????????????????GetExitCodeProcess(?hProcess,?lpExitCode);
19????????????????????????CloseHandle(hProcess);
20????????????????????????return;
21???????????????????}?//如果文件讀完,退出程序
22????
23????????mr?=?waveOutUnprepareHeader(hWaveOut,pHdr,sizeof(WAVEHDR));//清空設備緩存
24????????//判斷是否成功
25????????DWORD?dwBytesRequire?=?g_dwBytesleft?<?BUFFER_LENGTH????g_dwBytesleft:BUFFER_LENGTH;??//考慮剩余數據不足Buffer長度的情況
26????????
27????????ReadFile(g_fh,?pHdr->lpData,?dwBytesRequire?,&dwBytesRead,?NULL);
28????????
29//判斷是否成功
30????????g_dwBytesleft?-=?dwBytesRead;
31
32????????mr?=?waveOutPrepareHeader(hWaveOut,pHdr,sizeof(WAVEHDR));
33????????//判斷是否成功
34????????g_res0=?waveOutWrite(g_hWaveOut,?pHdr,?sizeof(WAVEHDR));
35????????//判斷是否成功
36}
37
38方法二:callback_event
39DWORD?WaveThreadFunc(DATABUFFER*?pBuffer)
40{
41????printf("start?thread?");
42????DWORD?dwBytesRead?=?NULL;
43????g_bEndThread?=?FALSE;
44????int?i?=?0;??
45????while(!g_bEndThread)
46????{
47??
48????????DWORD?dw?=?WaitForSingleObject(g_hWaveEvent,0);?//等待事件信號
49????????if(dw?==?WAIT_OBJECT_0)
50????????{????
51????????????printf("start?switch?");
52????????????
53????????????{
54????????????????pBuffer?=?&g_WaveBuffers[i];?//依次取各個Buffer
55????????????????if((pBuffer->Hdr.dwFlags?&&?WHDR_DONE)?==?WHDR_DONE)
56//Buffer內數據已經播放完成
57????????????????{?
58????????????????????waveOutUnprepareHeader(g_hWaveOut,&pBuffer->Hdr,sizeof(WAVEHDR));
59????????????????????DWORD?dwBytesRequire?=?g_dwBytesleft?<?BUFFERSIZE????g_dwBytesleft:BUFFERSIZE;
60????????????????????ReadFile(g_fh,?pBuffer->Hdr.lpData,?dwBytesRequire?,&dwBytesRead,?NULL);
61????????????????????g_dwBytesleft?-=?dwBytesRead;
62????????????????????waveOutPrepareHeader(g_hWaveOut,&pBuffer->Hdr,sizeof(WAVEHDR));
63????????????????????waveOutWrite(g_hWaveOut,?&pBuffer->Hdr,?sizeof(WAVEHDR));
64????????????????????
65????????????????}
66????????????????
67????????????}
68????????????i++;
69????????????if(i?==?NBUFFERS)
70????????????????i?=?i%NBUFFERS;
71????????????ResetEvent(g_hWaveEvent);
72????????}
73????}
74????ExitThread?(0);
75????return?0;
76}
Walzer評點:
文章給全了用CALLBACK FUNCTION和CALLBACK EVENT兩種處理方式的SAMPLE CODE。但要特別注意,這只是個DEMO,在多線程的項目中,WAVEOUT CALLBACK處理中調用WaveOutUnprepare甚至WaveOutWrite是很危險的,具體見我那篇《WaveOutReset的N種死法》。
本文轉自Walzer博客園博客,原文鏈接:http://www.cnblogs.com/walzer/archive/2008/02/20/1074400.html,如需轉載請自行聯系原作者
總結
以上是生活随笔為你收集整理的用API函数播放wav文件声音不连续的解决方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 决定将本博客技术知识从VS.Net转型S
- 下一篇: Android Studio在线安装An