網(wǎng)站運(yùn)營(yíng)seo文章大全提供全面的站長(zhǎng)運(yùn)營(yíng)經(jīng)驗(yàn)及seo技術(shù)!關(guān)鍵字: directsound 9 vb.net directx 聲音控制 聲音引擎 作者:董含君
轉(zhuǎn)載請(qǐng)注明來自 http://blog.csdn.net/a11s
目的:制作一個(gè)聲效控制器,減少內(nèi)存占用量,封裝directsound,低cpu消耗,滿足人的實(shí)際需要,以及易用性提高.
本來不打算繼續(xù)的,前幾天跟denghe研究了一下相關(guān)的問題.討論到太多的音效對(duì)機(jī)器的影響,尤其是內(nèi)存占用以及釋放.發(fā)現(xiàn)有必要做一個(gè)專門管理音效的控制器.
由于跟denghe的制作目的不同,所以實(shí)現(xiàn)起來大相徑庭.他全部都要實(shí)現(xiàn)gameobject接口,對(duì)于游戲來說,這是可行的.但是我做控制器的目的并不僅僅是用于游戲.而是更加像是提供一個(gè)處理聲音的服務(wù).我還不敢稱它為聲音引擎.畢竟可以實(shí)現(xiàn)的功能還比較少.
問題的出現(xiàn)
1 當(dāng)你打算播放音效的時(shí)候(就是wav),你可以new buffer(filename , ....) 這個(gè)我們都知道的. 但是new 之后,整個(gè)wav將會(huì)被裝入內(nèi)存.我們稱之為靜態(tài)載入.當(dāng)你進(jìn)攻敵人的時(shí)候,比如子彈的音效或者刀劍的音效.很多都是重復(fù)的.這時(shí),你可以用剛才new 出來的 buffer 直接 play.
2 問題如果這么簡(jiǎn)單我就沒有必要寫此文了.當(dāng)一個(gè)聲音沒有播放完成,另外一個(gè)重復(fù)的聲音接著響起的時(shí)候,很顯然不能直接play了,那么你怎么辦?先stop它然后重新從頭開始播放?很顯然,也不行.那么直接再new 一個(gè)可以了吧.當(dāng)然可以,除非你自己做混音(需要很多聲學(xué)的知識(shí),以及數(shù)學(xué)),否則只能使用兩個(gè)buffer不同時(shí)間同時(shí)播放的方法.
3 看似解決了問題.我們把問題繼續(xù)推廣到多個(gè)音效(wav),以及大量重復(fù)播放的時(shí)候.用問題2的解決辦法 new 出大量的buffer 就會(huì)出現(xiàn)問題了. 不但管理起來十分復(fù)雜,而且他會(huì)消耗掉恐怖的內(nèi)存.并且你的系統(tǒng)資源開銷非常可觀. 看似這樣的現(xiàn)象再游戲中是很容易出現(xiàn)的. 所以迫切需要一種管理方法應(yīng)對(duì)這種現(xiàn)象.
于我不同的是,denghe提供了一個(gè)soundpool方案,就是依賴游戲的腳本引擎.管理wav文件的載入以及卸載.每個(gè)有效的wav在每個(gè)場(chǎng)景載入的時(shí)候就創(chuàng)建多個(gè)備份,你可以給每個(gè)wav建立一定數(shù)目的緩沖buffer.比如建立4個(gè),然后根據(jù)需要自動(dòng)指向下一個(gè).然后在4個(gè)之間循環(huán).對(duì)于較短的音效而言,完全可以滿足人類聽覺的需要.所以對(duì)于游戲來說可以從根本上解決內(nèi)存的無限浪費(fèi)的局面,而且每種音效都不互相干擾..只要有可靠的腳本引擎負(fù)責(zé)加載以及釋放即可.所需的總空間為 文件的總占用體積+ 每個(gè)文件的buffer數(shù)量*該文件的大小,實(shí)際上很多音效是與用戶交互的,所以沒辦法預(yù)料某些音效的實(shí)際頻率.做最壞的打算,你付出的實(shí)際內(nèi)存=wav文件的個(gè)數(shù)*buffer數(shù)量.對(duì)于所需音效數(shù)量較小的程序來說,可以符合要求.但是某些較大的場(chǎng)景也許付出的代價(jià)需要重新考慮了.但是這個(gè)方案對(duì)游戲來說是比較好的方案.
4 我的解決方案:
4.1 建立layer: 在我看來,聲音可以分層次的.比如一個(gè)格斗游戲.player1說得話,可以放在layer1. player 說的話可以放在layer2,格斗時(shí)的聲效--就是那些噼里啪啦的聲音 可以放在layer3 , 場(chǎng)景開始的時(shí)候 "ready go",以及"k.o." 都可以放在layer4
4.2 建立layer緩沖區(qū):這樣分類完成之后,你會(huì)發(fā)現(xiàn),如果layer之間相互影響是不合適的,但是layer內(nèi)部,人說話的時(shí)候,如果同時(shí)出現(xiàn)兩句話是不是就有點(diǎn)不自然了?? 或者打斗的時(shí)候,那些噼里啪啦的聲音過于常見,而且比較短.人們熱火朝天的時(shí)候,不會(huì)可以在乎是否剛才的已經(jīng)播放完?? 把這些互斥的因素放在一個(gè)同一個(gè)layer里面是符合實(shí)際需要的.
4.3 建立步驟:
為了適應(yīng)速度的需要,所以不能考慮再游戲進(jìn)行的時(shí)候從硬盤加載.所以一開始就要全部加載到內(nèi)存.播放的時(shí)候由于每個(gè)wav只有一個(gè)buffer,所以需要?jiǎng)討B(tài)的創(chuàng)建副本進(jìn)行播放.副本的管理可以交給控制器.比如play 方法.play( soundkey,targetlayer)
4.4 性能分析: 主要是內(nèi)存占用. 所需內(nèi)存= 每個(gè)文件的大小的和 + layer的數(shù)量*每個(gè)layer中buffer的數(shù)量
這些都是可控的,當(dāng)然buffer越大,效果越好,而layer往往是根據(jù)實(shí)際需要一開始就確定好的,所以內(nèi)存使用可以穩(wěn)定在一個(gè)數(shù)值,值得慶幸的是,這個(gè)數(shù)值是可控的,甚至可以根據(jù)計(jì)算機(jī)的內(nèi)存容量臨時(shí)改變buffer的數(shù)量(呵呵,一般不會(huì)有人會(huì)這么想的)
5 關(guān)鍵技術(shù)問題: 看似上面的想法不錯(cuò),但是要實(shí)行起來,下列問題需要考慮
5.1 如何管理全部的buffer?對(duì)于習(xí)慣了面向?qū)ο蟮奈覀円苍S很少考慮這個(gè)問題,你可以用任何的高級(jí)手段,甚至想到用dataset.... 不是不可一,但是我比較古典,使用了一個(gè)buffer的二維數(shù)組,已經(jīng)夠用了.曾經(jīng)想用arrylist,但是想不出他跟數(shù)組相比能給我?guī)矶嗌賹?shí)際的優(yōu)勢(shì).
5.2 如何決定在哪個(gè)buffer播放? 既然buffer是數(shù)組或者其他形式,根據(jù)layer的劃分,從屬于layer.我僅僅針對(duì)二維數(shù)組來說,buffer[tarlayer][tarbuffer] 正好填充二維數(shù)組的兩個(gè)參數(shù).
5.3 既然是循環(huán)利用的,如何指定合適的buffer?
這個(gè)問題比較復(fù)雜,我封裝在了控制器內(nèi)部,用一個(gè)枚舉的辦法指定一個(gè)合適的buffer,一下是步驟
function enumbuffer(tarlayer as integer) as integer 傳遞過來要使用那個(gè)layer,返回合適的bufferid
a.循環(huán)檢查當(dāng)前l(fā)ayer是否有空閑的buffer,如果還有為nothing 或者 null 的buffer,毫無疑問他將會(huì)是最合適的buffer,所以直接退出循環(huán)返回他的id
b.如果不滿足a,那么看看是否有disposed或者已經(jīng)stop的buffer,他們已經(jīng)不再播放了.也是合適的,找到一個(gè)之后也就沒有必要繼續(xù)循環(huán),直接返回該id
c. 如果不滿足b,那么遍歷當(dāng)前l(fā)ayer的全部buffer,找到播放時(shí)間最長(zhǎng)的buffer.播放時(shí)間長(zhǎng)意味著來到的時(shí)間最長(zhǎng).我們可以請(qǐng)他休息了,返回這個(gè)id
d.如果滿足c的有多個(gè),那么按照第一個(gè)返回,或者最后一個(gè)返回.
5.4 播放的時(shí)候我們要注意什么?
為了節(jié)約空間當(dāng)然要清空 剛才被枚舉出來的buffer,然后創(chuàng)建一個(gè)wavbuffer列表的一個(gè)副本進(jìn)行播放.
由于這時(shí)不能從硬盤得到資料,所以只能考慮用buffer.clone()的方法,或者直接new 一個(gè)新的buffer(利用wavbuffer的stream)
6 具體結(jié)構(gòu)說明
類: xsoundlist 實(shí)質(zhì)是一個(gè)filestream的hashtable,提供一個(gè)關(guān)鍵字,一個(gè)filename,創(chuàng)建一個(gè)filestream..起到了存儲(chǔ)器的作用,將原始資料在這里集合,方便控制器讀取
類:xbgmlist 跟xsoundlist類似,不過這里用來存儲(chǔ)較大的文件,不使用靜態(tài)加載,提供多種格式.用于背景音樂(難道你打算加載cd音軌或者幾分鐘的wav到內(nèi)存?太瘋狂了)
類:xsoundmanex 控制器.具體管里播放哪一個(gè)以及內(nèi)部緩沖的數(shù)量,音量大小等等.也就是對(duì)directsound的封裝類,使用它可以用layer的思想管理soundbuffer
類:xsoundman 標(biāo)準(zhǔn)控制器,由于xsoundmanex使用了太多的directsound特性,不符合oop封裝的思想,所以對(duì)于普通應(yīng)用而言,應(yīng)當(dāng)使用這個(gè)來代替xsoundmanex,除非你打算重新封裝原始的ex.
類:xsoundsimp 簡(jiǎn)易控制器.只要有一個(gè)layer就夠用,超級(jí)簡(jiǎn)單的控制器,第一步加載,第二步播放 沒了,已經(jīng)重寫了析構(gòu),不必?fù)?dān)心泄漏,甚至每次新建一個(gè)xsoundsimp也比你直接new 多個(gè)buffer 節(jié)省內(nèi)存,而且不必進(jìn)行directsound的初始化
7 附加的功能
聲音音量的調(diào)整,背景音樂與soundbuffer的分離處理,動(dòng)態(tài)加載wav(雖然不建議這么做).循環(huán)播放.等等,如果不夠用可以從xsoundmanex繼續(xù)封裝
8 已知存在的不足以及下個(gè)版本的計(jì)劃.
layer之間的音量并沒有獨(dú)立,這點(diǎn)有悖于layer之間的互不干涉.
bgm沒有區(qū)分layer,由于設(shè)計(jì)的時(shí)候,判定bgm劃分layer的意義不大.所以設(shè)定bgm只有一個(gè)layer.中間使用也是競(jìng)爭(zhēng)的.雖然用建立較大的bgmbuffer可以環(huán)節(jié)這方面的不足(沒關(guān)系,動(dòng)態(tài)加載,內(nèi)存消耗不是很大).但是對(duì)于不了解其中機(jī)制的使用者來說會(huì)覺得不可思議.而且layer內(nèi)部競(jìng)爭(zhēng),一旦超出buffer最大值可能會(huì)影響背景音樂的使用.比如某些音效體積太大,程序人員將其作為bgm添加,這樣看起來節(jié)約,但是存在隱患.除非你能確保滿足枚舉條件b.
9 程序演示以及源代碼介紹
第一個(gè)簡(jiǎn)單應(yīng)用,按下響應(yīng)的鍵盤按鍵會(huì)發(fā)出音效,可以像彈鋼琴一樣.(你想做軟波表么?)
第二個(gè)演示是利用文本驅(qū)動(dòng)聲音的播放,可以"錄音"哦
第三個(gè)演示是頻率的重復(fù)對(duì)內(nèi)存以及cpu的影響(測(cè)試性能),包括背景音樂的播放以及l(fā)ayer的使用
第四個(gè)被我隱藏了,本來做個(gè)游戲的.時(shí)間不是很多,沒有完成,有興趣的化可以繼續(xù)完善
10 下載地址暫時(shí)無法提供,因?yàn)槲艺也坏降胤介L(zhǎng)時(shí)間穩(wěn)定的存放我的代碼,大約800k,而且csdn的blog暫時(shí)不提供rar的上傳.需要的話,請(qǐng)加qq群:589626 c#游戲開發(fā),群的共享里面有rar的下載
==============end================