版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
移動行業(yè)信息化ー編寫安全的SymbianC++游戲代碼摘要Symbian游戲是運(yùn)行在手機(jī)上的游戲,它不能干擾手機(jī)正常的通訊功能,對操作系統(tǒng)和其它應(yīng)用程序必須友善。而在首次編寫SymbianC++游戲時,我遇到了無數(shù)奇怪的問題,其中大部分問題出在內(nèi)存極端不足,打開太多應(yīng)用程序,屏幕保護(hù)探出,接到短信、電話等特殊情況下。本文獻(xiàn)給使用NokiaSymbian60SDK各個版本開發(fā)游戲軟件的程序員。雖然本文主要是針對游戲軟件,但是大部分內(nèi)容對一般應(yīng)用軟件也同樣適用。1,聲明為了避免良心的譴責(zé),首先我必須承認(rèn)一點(diǎn),我本人并不是靠SymbianC++糊口。除了forum,上的文章和SDK,我也沒有看過任何關(guān)于Symbian的書籍。只是偶然的,我在天津猛瑪游戲公司(www.mammothworld,com)認(rèn)識并接觸了Symbian〇我從零起步,寫出了一個蹩腳的Symbian游戲引擎并在3650、7650上開發(fā)了一些游戲。所以我對Symbian的掌握完全是出于自己的猜測和理解,雖然
本文缺乏權(quán)威,但至少都是經(jīng)驗(yàn)之談,容易理解。概述Symbian游戲是運(yùn)行在手機(jī)上的游戲,它不能干擾手機(jī)正常的通訊功能,對操作系統(tǒng)和其它應(yīng)用程序必須友善。而在首次編寫SymbianC++游戲時,我遇到了無數(shù)奇怪的問題,其中大部分問題出在內(nèi)存極端不足,打開太多應(yīng)用程序,屏幕保護(hù)探出,接到短信、電話等特殊情況下。其實(shí)如果養(yǎng)成嚴(yán)謹(jǐn)?shù)拇a風(fēng)格,進(jìn)行足夠的錯誤處理,大部分問題本可以避免。為了解決它們,我很是花了一番功夫,所以在此把我的一些教訓(xùn)、經(jīng)驗(yàn)寫出,希望大家能避免犯同樣的錯誤。如果你不是業(yè)余愛好者,而是為ー個認(rèn)真的開發(fā)商工作,特別是如果你的產(chǎn)品需要通過SymbianSigned認(rèn)證(),你就必須更加小心的對待本文提出的問題。SymbianSigned是ー個針對Symbian應(yīng)用程序的認(rèn)證,想要通過它,你的應(yīng)用程序必須通過一系列嚴(yán)格的測試。認(rèn)證對應(yīng)用程序的文件管理、內(nèi)存使用、系統(tǒng)事件響應(yīng)、網(wǎng)絡(luò)、資費(fèi)和私人數(shù)據(jù)等都有一定的要求。如果想了解SymbianSigned認(rèn)證的詳細(xì)內(nèi)容,可以去它們的網(wǎng)站下載白皮書。
異常處理雖然我們都知道任何ー個new(ELeave)或者帶有L后綴的函數(shù)都可能拋出異常,但是很多業(yè)余的愛好者還是會忽視SymbianC++中異常處理的重要性。雖然有些函數(shù)只有在極其罕見的情況下オ會拋出異常。但不是危言聳聽,如果你不寫代碼捕捉并處理它們,應(yīng)用程序就會遇到〃系統(tǒng)錯誤”。普通C++使用throw拋出異常。異常拋出后,棧會不?;貪L,直到遇到最近ー層catch為止。SymbianC++中的異常處理不使用try-catch和throw。但是它的處理機(jī)制和標(biāo)準(zhǔn)C++很是類似,區(qū)別僅僅是它只能拋出ー個整數(shù)錯誤碼,而不是ー個任意對象。我將從異常的拋出、捕捉、處理三方面講解這部分內(nèi)容。1,拋出異常SymbianC++中,有下面幾種情況下會拋出異常:使用靜態(tài)函數(shù)User::Leave拋出異常。這個函數(shù)就是最基本的異常產(chǎn)生函數(shù)。下面講的其它拋出方式都可以轉(zhuǎn)化為User::Leave〇使用靜態(tài)函數(shù)User::LeaveIfError把錯誤碼轉(zhuǎn)化為異常。有些函數(shù)比如CFbsBitmap::Create()有一個Tint的返回值。如果遇到錯誤,這些函數(shù)就會返回非KErrNone的錯誤值。此時,可以使用LeavelfError把這個返回值轉(zhuǎn)化為異常。比如:User::LeavelfError(bmp->Create(iSize,EColor4k);其實(shí)
LeavelfError就是if(returnValue!=KErrNone)User::Leave(returnValue);使用new(ELeave)申請內(nèi)存。如果沒有足夠內(nèi)存可用,此操作產(chǎn)生一個KErrNoMemory異常。比如TText8*p=new(ELeave)TText8[20];相當(dāng)于TText8*p=newTText8[20];if(p==NULL)User::Leave(KErrNoMemory);調(diào)用帶有L后綴的函數(shù)。Symbian系統(tǒng)的命名規(guī)范中要求,每ー個可能Leave的函數(shù)都要有后綴L。包含有帶L的內(nèi)層函數(shù)調(diào)用的外層函數(shù)也必須加上L。這類函數(shù)中最常見的就是NewL,NewLC和ConstructLo這個規(guī)范比你想象的要重要。因?yàn)樗o其他程序員ー個暗示,提示他們對這些函數(shù)進(jìn)行保護(hù)。1.3.2.捕捉異常類似標(biāo)準(zhǔn)C++的catch語句,SymbianC++的TRAP關(guān)鍵字可以對一個可能產(chǎn)生異常的函數(shù)進(jìn)行保護(hù),并且捕獲到異常值。比如:TinterrorCode;TRAP(errorCode,SomeDangerousFuncL());/Z保護(hù)執(zhí)行SomeDangerousFuncL()函數(shù)if(errorCode!=KErrNone)!//捕捉到了一個異常,在這里添加處理異常的代碼類似的TRAPD省去了你聲明一個局部變量的麻煩。頭兩行代碼可以簡
寫成:TRAPD(errorCode,SomeDangerousFuncL());1.3.3.處理異常對于不同的異常當(dāng)然有不同的處理方法(廢話:-))。我們以最常見的捕獲到代表內(nèi)存不足的KErrNoMemory異常為例講解。注意在Container,AppUi等類的構(gòu)造過程中,你不需要加入對內(nèi)存不足的保護(hù)。因?yàn)檫@一切系統(tǒng)已經(jīng)為你做好了。系統(tǒng)會彈出一個對話框報(bào)告內(nèi)存不足。根據(jù)你的操作系統(tǒng)版本不同,這可能是中文的,也可能是英文或者其它語言的。如果你不信,可以在AppUi或者Container的ConstructL中寫一行User::Leave(KErrNoMemory)試試看。我試驗(yàn)的結(jié)果如下:除了上面說的特殊情況,你可以簡單的彈出ー個對話框,告訴用戶沒有足夠的內(nèi)存運(yùn)行程序,并且安全的關(guān)閉程序。比如我的游戲程序就是這樣處理的:voidCStageManager::DoGameFrame()
TRAPD(error,DoGameFrameProtectedL());if(error==KErrNoMemory)(StopGame();m_noMemoryDlg->ExecuteLD(R_KEY_INVALID_DIALOG);Exit();//CallCAknAppUi::RunAppShutter()}elseif(error!=KErrNone){User::PanicGL(/zSomeothererror."),error);})其中noMemoryDlg是直接或者間接在Container的ConstructL中創(chuàng)建的://inheaderfileCAknQueryDialog*mnoMemoryDlg;//somewhereinConstructLTBuf<128>errMsg;_LIT(formater,"Notenoughmemory.Pleaseclosesomeapplications.");errMsg.Copy(formater);
mnoMemoryDlg=new(ELeave)CAknQueryDialog(errMsg,CAknQueryDialog::EErrorTone);當(dāng)然,你也可以制作一個精美的圖片來報(bào)告內(nèi)存不足,等待用戶按任意鍵再退出。不過載入這個圖片也可能會失敗,所以至少在這個圖片成功載入之前,你還是需要系統(tǒng)對話框來報(bào)告的。值得一提的是,你不一定需要退出程序,或者你可以稍后重試申請內(nèi)存,幸運(yùn)的話,沒準(zhǔn)第二次就能成功。這是因?yàn)镾ymbian系統(tǒng)會在內(nèi)存不足時自動關(guān)閉ー些應(yīng)用程序。我覺得這是Symbian系統(tǒng)ー個比較奇怪的設(shè)計(jì)。通常應(yīng)用程序在AppUi的HandleCommandL中會響應(yīng)EEikCmdExit消息,并且調(diào)用CAknAppUi::Exit()函數(shù)(如下代碼)。這使得應(yīng)用程序可以在應(yīng)用程序管理器中用C鍵結(jié)束掉。這也使得Symbian操作系統(tǒng)有機(jī)會在內(nèi)存不足時通過這個渠道自動關(guān)閉ー些應(yīng)用程序。// //CFlyAppUi::HandleCommandL(TintaCommand)//takescareofcommandhandling// //voidCFlyAppUi::HandleCommandL(TintaCommand)switch(aCommand)
caseEEikCmdExit:Exit();break;//TODO:AddYourcommandhandlingcodeheredefault:break;})坦白說我沒有嘗試過重試申請內(nèi)存這個辦法,不過我想是可行的。1.3.4.?;貪L和對象的安全析構(gòu)上面說到在遇到某些異常時,你可以選擇彈出對話框并且結(jié)束程序,其實(shí)這會比你想象的要困難ー些。因?yàn)镃++可不像Java那樣有托管堆進(jìn)行垃圾收集。不過好在C++棧會自動回滾,棧上的對象會被銷毀。如果你此時調(diào)用CAknAppUi::RunAppShutter()結(jié)束程序,那么AppUi,Container的析構(gòu)函數(shù)會依次被調(diào)用,引起你自己創(chuàng)建對象的析構(gòu)函數(shù)也依次被調(diào)用。那么堆上的對象也要被銷毀。可是,請記住,異常隨時隨處可能發(fā)生,使對象處于ー種"半構(gòu)造"的狀態(tài)。此時析構(gòu)函數(shù)被調(diào)用可能會造成對無效指針的訪問錯誤。請看下面這個例子,它犯了兩個常見的錯誤:classBadExample
protected:TText8*m_pBuf;TText8*m_pBuf2;public:staticBadExample*NewL(){BadExample*self=new(ELeave)BadExample();self->ConstructL();returnself;}voidDeleteBuf(){deletempBuf;}voidRebuildBufLO{mpBuf=new(ELeave)TText8[256];}private:BadExample();deletempBuf;deletem_pBuf2;}voidConstructL(){mpBuf=new(ELeave)TText8[256];m_pBuf2=new(ELeave)TText8[256];});假設(shè)我們在AppUi的ConstructL中使用BadExample::NewL()來構(gòu)造對象,在AppUi的析構(gòu)函數(shù)中delete這個對象。下面我們分析一下可能遇到的問題:首先,在函數(shù)NewL中,self指針沒有被保護(hù),試想如果self->ConstructL()一句拋出異常。那么這個self指針指向的對象就沒有return給外界(也就是AppUi),這個對象就永遠(yuǎn)“丟失了”,造成了內(nèi)存泄露。正確的做法是使用CleanupStack對它進(jìn)行保護(hù)。Cleanupstack至少能保證在程序退出時壓入其中的對象都能銷毀。staticBadExample*NewL()BadExample*self=new(ELeave)BadExample();
CleanupStack::PushL(self);self->ConstructL();CleanupStack::Pop();returnself;)但是注意,此處還有一個微妙的內(nèi)存泄露。仔細(xì)看看CleanupStack::PushL()的聲明:IMPORT_CstaticvoidPushL(TAny*aPtr);IMP0RT_CstaticvoidPushL(CBase*aPtr);IMPORTCstaticvoidPushL(TCleanupItemanltem);如果傳入的指針是CBase指針,那么CBase的虛析構(gòu)函數(shù)(virtual?CBase())就能保證對象在銷毀時正確的調(diào)用析構(gòu)函數(shù)。可是本例中BadExample不是從CBase中派生,那么對象只能做很有限的銷毀,根本不會調(diào)用析構(gòu)函數(shù)。所以,如果ConstructL是由于第二個內(nèi)存申請m_pBuf2失敗,那么m_pBuf申請的內(nèi)存就永遠(yuǎn)不會回收。所以正確的做法是,讓BadExample從CBase派生。classBadExample:publicCbase其次,我們并沒有為m_pBuf和m_pBuf2賦初值,在Release版中他們的值是隨機(jī)的。那么,如果m_pBuf2的申請失敗,析構(gòu)函數(shù)還是會執(zhí)行deletem_pBuf2,試圖刪除ー個無效指針。正確的做法是在構(gòu)造函數(shù)中為m_pBuf和m_pBuf2賦初值NULL〇因?yàn)闃?biāo)準(zhǔn)C++規(guī)定,deleteー個空指針不做任何操作。不過實(shí)際上,如果對象從CBase
派生,這ー步是沒有必要的,因?yàn)镃Base能保證派生類的成員變量在構(gòu)造時自動清零。最后,動態(tài)的使用DeleteBuf和RebuildBufL是不安全的。如果你先用DeleteBuf刪除了這個對象,那么m_pBuf就是ー個壞指針。可是緊接著的RebuildBufL可能會失敗。此時如果析構(gòu)函數(shù)被調(diào)用,還是會產(chǎn)生delete無效指針的錯誤。正確的做法是,在DeleteBuf中,把m_pBuf設(shè)為NULL〇總結(jié)上面說到的幾點(diǎn),完整的安全的代碼是:classBadExample:publicCBaseIprotected:TText8*m_pBuf;TText8*m_pBuf2;public:staticBadExample*NewL(){BadExample*self二new(ELeave)BadExample();CleanupStack::PushL(self);self->ConstructL();CleanupStack::Pop();returnself;
voidDeleteBuf()deletempBuf;m_pBuf=NULL;}voidRebuildBufLO(mpBuf=new(ELeave)TText8[256];}private:~BadExample(){deletempBuf;deletem_pBuf2;}voidConstructL(){mpBuf=new(ELeave)TText8[256];m_pBuf2=new(ELeave)TText8[256];
1.4.安全的圖像引擎SymbianC++游戲的2D圖像顯示部分一般由下面幾個類組成:圖像一封裝了一個CWsBitmap。是基本的圖片資源。支持圖像之間的各種貼圖和混合操作。雙緩沖一一個和屏幕分辨率、色深相等的圖像。直接寫屏支持ー復(fù)合一個CDirectScreenAccess對象,實(shí)現(xiàn)MDirectScreenAccess接口。負(fù)責(zé)直接寫屏的安全處理。比如來電、屏保時適時的停止和開啟直接寫屏與游戲邏輯。繪圖類ー負(fù)責(zé)在圖像中繪圖。它不是對Gc的封裝,而是通過直接修改圖像內(nèi)存區(qū)進(jìn)行繪圖。位圖字體類ー使用預(yù)先創(chuàng)建的位圖資源寫字。如下圖就是ー個預(yù)先創(chuàng)建的位圖資源。優(yōu)點(diǎn)是速度快,缺點(diǎn)是無法支持大字符集合,比如中文。字體緩沖區(qū)類ー還是使用Gc的DrawText函數(shù)繪制文字。但是同時用ー張位圖作為ー個緩沖區(qū)存儲最近繪制的文字。既能支持大字符集合,速度也很快。如果需要學(xué)習(xí)圖形和直接寫屏的基礎(chǔ),請參考ProgrammingGamesin
C++vl.0(www.forum,/main/1.6566.21.00.html)〇本文主要針對圖像類和直接寫屏類講幾個容易被忽略的問題。1.4.1.圖像類的直接內(nèi)存訪問貼圖是2D游戲最主要的畫面操作。為了實(shí)現(xiàn)快速的貼圖,或者實(shí)現(xiàn)某種混合效果,就不能再使用CFbsBitGc的BitBlt或者BitBltMasked進(jìn)行貼圖,而必須自己得到圖片的內(nèi)存地址,直接讀寫其中的數(shù)據(jù)。在讀寫圖片內(nèi)存地址的過程中,有幾點(diǎn)需要加以注意。首先,只有當(dāng)源圖片和目標(biāo)圖片色深相等時,オ更容易進(jìn)行貼圖操作。所以,再載入圖片的過程中,我習(xí)慣把非4k色的圖片轉(zhuǎn)化為4k色。之所以選擇4k色是因?yàn)樗彩呛笈_緩沖區(qū)的色深。下面的代碼通過轉(zhuǎn)換可以保證iImage是4k色的圖像。//Makesurethatwehavea4KcolordepthimageiniImageif(iImage->DisplayMode()!=EColor4K){//Create4kcolorimageCFbsBitmap*image=new(ELeave)CWsBitmap();CleanupStack::PushL(image);User::LeavelfError(image->Create(iSize,EColor4K));//CreatedeviceCFbsBitmapDevice*device=CFbsBitmapDevice::NewL(image);
CleanupStack::PushL(device);CFbsBitGc*gc;User::LeavelfError(device->CreateContext(gc));CleanupStack::PushL(gc);//Bitbittonewcolordepthgc->BitBlt(TPoint(0,0),iImage);//Destroycontextanddevice;CleanupStack::PopAndDestroy();//gcCleanupStack::PopAndDestroy();//deviceCleanupStack::Pop();//imagedeleteiImage;iImage=image;}其次,Symbian系統(tǒng)在內(nèi)存匱乏時會進(jìn)行碎片整理。所以如果簡單的用CFbsBitmap::DataAddress獲取內(nèi)存首地址并開始讀寫,那么可能在你讀寫的過程中,圖片已經(jīng)被悄悄的移動了位置,你讀寫的就是ー塊無效的內(nèi)存區(qū)域。解決這個問題的辦法是在獲取首地址前,必須先鎖定圖像內(nèi)存區(qū)域。在高版本的60系列SDK中(比如2.0,2.1),有LockHeap和UnlockHeap函數(shù)可以完成這個操作。但是在低版本的SDK中(比如0.9,L0),這兩個函數(shù)是私有的。我們必須通過TBitmapUtil鎖定內(nèi)存。但是不一定必須使用TBitmapUtil的
SetPixel和GetPixel函數(shù)進(jìn)行位操作。下面是最基本的沒有關(guān)鍵色和Alpha通道的簡單貼圖代碼。voidCimage::RenderToBitmapL(CFbsBitmap*aBmp,TPointaPos,constTRect&aRect)I//在此計(jì)算貼圖目標(biāo)矩形區(qū)域/Z代碼略去/Z沒有關(guān)鍵色和蒙板的最簡單、最快情況if(!iKey&&iMask==NULL){/Z鎖定TBitmapUtilbmpUtil1(ImageLO);TBitmapUtilbmpUtil2(aBmp);bmpUtill.Begin(TPoint(0,0));bmpUtil2.Begin(TPoint(0,0),bmpUtill);/Z獲取首地址TUintl6*addr2=(TUintl6*)ImageL()->DataAddress();//sourceimageTUintl6*addr=(TUintl6*)aBmp->DataAddress();//targetbmpTintline=aBmp->ScanLineLength(
aBmpー〉SizeInPixels().iWidth,EColor4K)/2;Tintline2=iImage-〉ScanLineLength(//linelengthin16bitwordiImage-〉SizelnPixels().iWidth,EColor4K)/2;/Z計(jì)算掃描持續(xù)量和跳躍量Tintjump=line一rectw;Tintlasting2=rectw;Tintjump2=line2一lasting2;/Z獲取貼圖首地址TUintl6*p=addr+fromY*aBmp->SizeInPixels().iWidth+fromX;TUintl6*p2=addr2+line2*recty+rectx;//ThefirstpixeloutofinterestTUintl6*p2end=p2+line2*(toY-fromY-1)+lasting2+jump2;/Z開始掃描while(p2!=p2end)
/Z開始ー個掃描行TUintl6*p2endline=p2+lasting2;while(p2!=p2endline)(〃復(fù)制一個像素*p=*p2;/Z移動到下ー個像素p++;p2++;}//跳到下一行P+=jump;p2+=jump2;)/Z解鎖bmpUtil2.End();bmpUtill.End();return;/Z其它情況。有關(guān)鍵色等等.最后告訴大家?guī)讉€優(yōu)化的小竅門:
使用While循環(huán)直接把指針的比較作為循環(huán)結(jié)束條件。不要再多用ー個整數(shù)來控制循環(huán)。貼圖是個兩重循環(huán),如果你的代碼需要判斷是否支持關(guān)鍵色和Alpha通道等,盡量把判斷外移到循環(huán)之外。每個象素都進(jìn)行好幾個if判斷的開銷太不值得。比如上面的代碼,處理最簡單的情況時,while循環(huán)內(nèi)ー個if都沒有。4k色時,RGB內(nèi)存排列如下圖。所以未被使用的4位正巧可以用來存儲alpha通道。1.4.2.直接寫屏和特殊系統(tǒng)事件游戲軟件一般用CDirectScreenAccess進(jìn)行直接寫屏。大家都知道,WindowServer會在需要停止直接寫屏?xí)r回調(diào)MDirectScreenAccess::AbortNow接口函數(shù),在可以重新啟動時回調(diào)MDirectScreenAccess::Restart接口函數(shù)??墒蔷唧w在這兩個函數(shù)中做什么,SDK沒有過多的介紹。我在此說一下我的做法。如果你合理的處理了這兩個函數(shù),就可以輕松應(yīng)對來電、屏保、程序切換等事件。我們先說AbortNow,它的處理比較簡單。你之需在其中停止驅(qū)動游戲邏輯的計(jì)時器(一般是個CPeriodic對象),停止聲音模塊(一般是ー個CActive任務(wù))就可以了。值得費(fèi)些力氣的是Restart函數(shù),它并不是在應(yīng)用程序回到前臺,并
且可以進(jìn)行全屏直接寫屏?xí)rオ被回調(diào)。所以不能在此時武斷的恢復(fù)游戲邏輯,開始游戲。首先,你要調(diào)用CDirectScreenAccess::StartL()恢復(fù)直接寫屏。但是必須給這個函數(shù)加上TRAP保護(hù)。因?yàn)樗芸赡軖伋鯧ErrNotReady異常。如果遇到這個異常,那你就直接返回好了,因?yàn)橹苯訉懫链藭r并不能開始。接下來你需要檢查一下繪圖區(qū)域,看是否整個屏幕都可以被使用。如果不是,那也無需啟動游戲邏輯,只需要用最后保留的后臺緩沖區(qū)的內(nèi)容更新直接寫屏區(qū)域即可。第三種情況,如果直接寫屏成功啟動,并且整個屏幕都可以被繪制,オ啟動游戲邏輯,啟動聲音等其它模塊。完整的代碼如下:voidCEngine::AbortNow(RDirectScreenAccess::TTerminationReasons/*aReason*/)I//Canceltimeranddisplayif(iGameTimer->IsActive())iGameTimer->CancelTimer();if(!iGameWorldPaused)(iGameWorldPaused=ETrue;iGameWorld->PauseGame();//Pauseaudiostreametc.
iPaused=ETrue;voidCEngine::Restart(RDirectScreenAccess::TTerminationReasons/*aReason*/)JTRAPD(error,SetupDirectScreenAccessL());switch(error){caseKErrNone:break;caseKErrNotReady:if(iDirectScreenAccess->IsActive())iDirectScreenAccess->Cancel();if(iGameTimer->IsActive())iGameTimer->CancelTimer();if(!iGameWorldPaused)(iGameWor1dPaused=ETrue;iGameWorld->PauseGame();
return;default:User::Panic(_L(wSetupDSAError"),error);}if(iPaused){if(iGameDawingArea==iRegion->BoundingRect())(iPaused=EFalse;if(!iGameTimer->IsActive())(iGameWorldPaused=EFalse;iGameWorld->ResumeGame();iGameTimer->Restart();})elsePauseFrame();
elseif(!iGameTimer->IsActive())4iGameTimer->Restart();voidCEngine::SetupDirectScreenAccessL()(//InitialiseDSAiDirectScreenAccess->StartL();//GetgraphicscontextforitiGc=iDirectScreenAccess->Gc();//GetregionthatDSAcandrawiniRegion=iDirectScreenAccess->DrawingRegion();//SetthedisplaytocliptothisregioniGc->SetClippingRegion(iRegion);
voidCEngine::PauseFrame()//Forcescreenupdate:thisisrequiredforWINS,butmay//notbeforallhardware:iDirectScreenAccess->ScreenDevice()->Update();//anddrawfromunchangedoffscreenbitmapiGc->BitBlt(TPoint(0,0),&(iDoub1eBufferedArea->GetDoub1eBufferedAreaBitmap()));iClient.Flush();));1.5.聲音處理我的引擎中使用CMdaAudioOutputStream和MMdaAudioOutputStreamCalIback完成聲音播放功能。它主要有三個類組成:CAudioStreamPlayero它復(fù)合CMdaAudioOutputStream,繼承CActive,實(shí)現(xiàn)MMdaAudioOutputStreamCallback接口。我們需要小心的維持緩沖區(qū)的大小以獲得低延遲播放。CActive不斷的建立新的
任務(wù),在RunL函數(shù)中估算緩沖區(qū)中的剩余數(shù)據(jù),向其中追加適當(dāng)?shù)臄?shù)據(jù),維持緩沖區(qū)的預(yù)期大小。CSimpleMixer0它實(shí)現(xiàn)CAudioGenerator接口。因?yàn)镃MdaAudioOutputStream是ー個單ー的流式播放器,所以需要寫ー個混音器進(jìn)行波形混合。這里波形混合就是簡單的數(shù)據(jù)相加?;煲羝饔性S多的聲道(channel)〇每個channel記錄了其中的CAudio指針和當(dāng)前播放位置。CAudiOo包含ー個音頻緩沖區(qū)。對每個聲音文件,我們還需要一個類把它載入到內(nèi)存緩沖區(qū)中。我不會在此講解如何實(shí)現(xiàn)音頻播放,那需要單獨(dú)的ー篇文章。如果你也使用這種方法實(shí)現(xiàn)聲音播放,我只想在此和大家討論兩個問題。需要學(xué)習(xí)聲音基礎(chǔ)的話,可以參考/article.php?id_article=l13。(可惜我當(dāng)時學(xué)習(xí)聲音時那篇文章和代碼找不到了)1.5.1,聲音的關(guān)閉和開啟因?yàn)檎麄€音頻系統(tǒng)是ー個拉的結(jié)構(gòu),音頻流從混音器那里拉數(shù)據(jù),混音器從音頻緩沖區(qū)中拉數(shù)據(jù)。所以,只要把CMdaAudioOutputStream和寫數(shù)據(jù)的CActive對象delete掉,聲音播放就全部停止了。在我的實(shí)現(xiàn)中,也就是deleteCAudioStreamPlayer對象即可。再想要開啟聲音,只需要重新創(chuàng)建
這個對象。這個實(shí)現(xiàn)的好處是程序的其它部分不需要保存聲音是否開啟這個狀態(tài)。因?yàn)镃Audio和CSimpleMixer對象是存在的,CAudio就可以把自己插入到Mixer的channel中,覺得自己好像在播放ー樣。其實(shí)因?yàn)镃AudioStreamPlayer根本沒有從Mixer向外拉數(shù)據(jù),聲音設(shè)備是完全停止的。但是在恢復(fù)聲音播放時有一點(diǎn)需要注意,恢復(fù)前需要清空混音器中的聲音數(shù)據(jù)。因?yàn)榻?jīng)過了長時間的運(yùn)行,混音器中的各個channel中已經(jīng)塞滿了各種聲音。如果此時突然打開,會傳出各種延遲了的雜音。1.5.2,特殊錯誤處理MMdaAudioOutputStreamCallback接口中的兒個回調(diào)函數(shù)MaoscOpenComplete>MaoscBufferCopied和MaoscPlayComplete都有一個錯誤碼參數(shù)。你不能忽略這個參數(shù)。比如MaoscPlayComplete函數(shù),是在音頻停止播放時被調(diào)用。停止播放的原因可能是多種多樣的。我們都知道要處理KErrUnderflow這個情況,這個錯誤嗎意味著混音器沒有及時的供給它音頻數(shù)據(jù)。此時需要重新啟動聲音流。但是還有一些情況比如KErrDied和KErrlnUse很容易被忽略。KErrDied發(fā)生在接聽電話時,此時聲音線程已經(jīng)死T,那么就需要重建整個音頻系統(tǒng)。KErrlnUse發(fā)生在收到短信時,此時聲音設(shè)備被搶占,用來播放短信提示音。此時你也需要重建整個
聲音系統(tǒng),但是此時不能立刻重建,否則還是ー樣的結(jié)果。你應(yīng)該等待幾秒鐘之后オ重建它。上面說的重啟聲音流和重建聲音系統(tǒng)深度不同。重啟聲音流在稍后的代碼中可以看到。其中RunAudioL向音頻流寫入了第一個聲音緩沖區(qū)。重建聲音系統(tǒng)在我的實(shí)現(xiàn)中就是指先delete再NewL創(chuàng)建CAudioStreamP1ayer對象。這三個錯誤的處理代碼如下://AudiostreamAPIcallback:Calledwhenplaybackhasfinished,voidCAudioStreamPlaye
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 稀土后處理工班組安全測試考核試卷含答案
- 鑄管制芯工安全知識競賽評優(yōu)考核試卷含答案
- 漁船機(jī)駕長常識評優(yōu)考核試卷含答案
- 海參池塘養(yǎng)殖培訓(xùn)
- 茶葉拼配師安全素養(yǎng)評優(yōu)考核試卷含答案
- 礦石破碎篩分工操作知識能力考核試卷含答案
- 橋梁工程培訓(xùn)
- 老年人入住老人教育培訓(xùn)制度
- 海上作業(yè)安全培訓(xùn)
- 酒店客房清潔保養(yǎng)制度
- 2025至2030蘑菇多糖行業(yè)發(fā)展趨勢分析與未來投資戰(zhàn)略咨詢研究報(bào)告
- 液壓爬模設(shè)備操作安全管理標(biāo)準(zhǔn)
- 渠道拓展與合作伙伴關(guān)系建立方案
- 2025年文化旅游產(chǎn)業(yè)預(yù)算編制方案
- 木工安全操作教育培訓(xùn)課件
- 護(hù)理洗胃考試試題及答案
- 2025年醫(yī)院精神科服藥過量患者應(yīng)急預(yù)案及演練腳本
- 軍人識圖用圖課件
- 廣東2025年事業(yè)單位招聘考試真題及答案解析
- 浙江杭州西湖區(qū)保俶塔實(shí)驗(yàn)校2026屆中考物理考試模擬沖刺卷含解析
評論
0/150
提交評論