用API函数播放wav文件声音不连续的解决方法
作為一個(gè)多媒體技術(shù)方面的初學(xué)者,我從wav文件的播放開始了解媒體播放的流程。
??? 于是從建立兩個(gè)線程開始,線程1用來(lái)將文件中的數(shù)據(jù)讀到Buffer中去,以后稱為讀線程,線程2用來(lái)將Buffer中的數(shù)據(jù)送到設(shè)備的緩存,以后成為寫線程。在最開始的時(shí)候,本著從易到難的精神,只開了一個(gè)buffer,我的想法很簡(jiǎn)單,先將數(shù)據(jù)寫進(jìn)這個(gè)Buffer,然后再送入設(shè)備進(jìn)行播放,結(jié)果是每個(gè)Buffer中間有個(gè)空隙,這件事情是明擺著的,只采用一個(gè)Buffer肯定會(huì)導(dǎo)致這樣的結(jié)果。在我看來(lái)是因?yàn)?#xff0c;設(shè)備將緩存中的數(shù)據(jù)播放完以后回頭去Buffer取數(shù)據(jù)時(shí)發(fā)現(xiàn)Buffer是空的,因而再去讀線程將PCM數(shù)據(jù)寫入Buffer,也就是說(shuō)設(shè)備有一段時(shí)間是無(wú)事可做的,聽起來(lái)當(dāng)然是斷斷續(xù)續(xù)的了。
??? 聽了高手的意見建立了一個(gè)包含兩個(gè)Buffer的隊(duì)列。
??? 在用緩沖隊(duì)列播放wav文件的時(shí)候卻始終遇到一個(gè)問題,在播放的時(shí)候會(huì)在每個(gè)buffer讀取數(shù)據(jù)的空隙產(chǎn)生停頓,這不是還和一個(gè)Buffer一樣?難道采用兩個(gè)Buffer也不行,要更多的Buffer?在網(wǎng)上查閱一些資料時(shí)發(fā)現(xiàn)理解上的偏差,兩個(gè)buffer的切換流程如下所示:
Buffer[0] read;
Buffer[1] read;
Buffer[0] write to device;
Buffer[0]read;
Buffer[1]write to device;
也就是說(shuō),在Buffer[0]寫入設(shè)備緩存之后就立刻再去取數(shù)據(jù),也就是要保證在每個(gè)時(shí)刻都至少有一個(gè)Buffer里面有數(shù)據(jù)。
????而我沒有預(yù)先將Buffer寫滿,結(jié)局自然是和一個(gè)Buffer一樣,想到這里我信心滿滿,覺得問題就要解決了,實(shí)際上還有很多問題。最終我放棄了建立兩個(gè)子線程的想法(實(shí)際也不需要)。
??? 有關(guān)Buffer的切換是想明白了,具體實(shí)施起來(lái)有兩種方法。兩種方法本質(zhì)上是相同的,只不過是在waveOutOpen的時(shí)候最后一個(gè)參數(shù)的設(shè)置不同。
方法一:使用CALLBACK_FUNCTION(回調(diào)函數(shù))。詳見wince help中關(guān)于WaveOutProc函數(shù)的說(shuō)明,只需要一個(gè)線程,通過回調(diào)函數(shù)查看系統(tǒng)消息WOM_DONE,來(lái)確定Buffer中的數(shù)據(jù)有沒有被播放完畢,如果播放完畢則首先清空設(shè)備緩存(waveOutUnprepare),然后寫B(tài)uffer,將Buffer中的數(shù)據(jù)送入設(shè)備緩存。還要判斷文件是否已經(jīng)讀完,如果讀完則跳出CallBack函數(shù)。
方法二:使用CALLBACK_EVENT。另外建立一個(gè)子線程。微軟方面提供的文檔告訴我們,應(yīng)該檢查dwFlags&&WHDR_DONE 是否為WHDR_DONE,以確保Buffer中的數(shù)據(jù)已經(jīng)被播放完畢。當(dāng)子線程開始執(zhí)行時(shí)要判斷這兩個(gè)參數(shù)為真時(shí),才能清空設(shè)備緩存。
??? 兩者的不同是,方法一中,系統(tǒng)會(huì)自動(dòng)將播放完畢的Buffer地址返回到程序中,當(dāng)callback函數(shù)被調(diào)用時(shí)可以直接使用;使用方法二,則要不斷地判斷Buffer中的數(shù)據(jù)是否已經(jīng)播放完畢。
ps:采用多少個(gè)只要每次填入的數(shù)據(jù)(播放時(shí)間)在1/50~1/70秒以上,就不會(huì)出現(xiàn)咔咔的聲音,和具體和設(shè)備性能以及聲卡驅(qū)動(dòng)有關(guān)。
?
?1方法一:callback_function?2void?CALLBACK?waveOutProc(HWAVEOUT?hWaveOut,UINT?uMsg,DWORD?dwInstance,
?3???????????DWORD?dwParam1,DWORD?dwParam2)
?4{
?5????WAVEHDR*?pHdr?=?(PWAVEHDR)dwParam1;?//?該參數(shù)由系統(tǒng)自動(dòng)返回
?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));//清空設(shè)備緩存
24????????//判斷是否成功
25????????DWORD?dwBytesRequire?=?g_dwBytesleft?<?BUFFER_LENGTH????g_dwBytesleft:BUFFER_LENGTH;??//考慮剩余數(shù)據(jù)不足Buffer長(zhǎng)度的情況
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);?//等待事件信號(hào)
49????????if(dw?==?WAIT_OBJECT_0)
50????????{????
51????????????printf("start?switch?");
52????????????
53????????????{
54????????????????pBuffer?=?&g_WaveBuffers[i];?//依次取各個(gè)Buffer
55????????????????if((pBuffer->Hdr.dwFlags?&&?WHDR_DONE)?==?WHDR_DONE)
56//Buffer內(nèi)數(shù)據(jù)已經(jīng)播放完成
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評(píng)點(diǎn):
文章給全了用CALLBACK FUNCTION和CALLBACK EVENT兩種處理方式的SAMPLE CODE。但要特別注意,這只是個(gè)DEMO,在多線程的項(xiàng)目中,WAVEOUT CALLBACK處理中調(diào)用WaveOutUnprepare甚至WaveOutWrite是很危險(xiǎn)的,具體見我那篇《WaveOutReset的N種死法》。
本文轉(zhuǎn)自Walzer博客園博客,原文鏈接:http://www.cnblogs.com/walzer/archive/2008/02/20/1074400.html,如需轉(zhuǎn)載請(qǐng)自行聯(lián)系原作者
總結(jié)
以上是生活随笔為你收集整理的用API函数播放wav文件声音不连续的解决方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 决定将本博客技术知识从VS.Net转型S
- 下一篇: oracle 动态注册和静态注册