版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
移動行業(yè)信息化-編寫安全的SymbianC++游戲代碼
摘要
Symbian游戲是運行在手機(jī)上的游戲,它不能干擾手機(jī)正常的通
訊功能,對操作系統(tǒng)和其它應(yīng)用程序必須友善。而在首次編寫
SymbianC++游戲時,我遇到了無數(shù)奇怪的問題,其中大部分問題出
在內(nèi)存極端不足,打開太多應(yīng)用程序,屏幕保護(hù)探出,接到短信、電
話等特殊情況下。
本文獻(xiàn)給使用NokiaSymbian60SDK各個版本開發(fā)游戲軟
件的程序員。雖然本文主要是針對游戲軟件,但是大部分內(nèi)容對一般
應(yīng)用軟件也同樣適用。
1.1.聲明
為了避免良心的譴責(zé),首先我必須承認(rèn)一點,我本人并不是
靠SymbianC++糊口。除了forum,上的文章和SDK,我也
沒有看過任何關(guān)于Symbian的書籍。只是偶然的,我在天津猛雞游戲
公司(www.mammothworld,com)認(rèn)識并接觸了Symbian。我從零起步,
寫出了一個蹩腳的Symbian游戲引擎并在3650、7650上開發(fā)了一些
游戲。所以我對Symbian的掌握完全是出于自己的猜測和理解,雖然
本文缺乏權(quán)威,但至少都是經(jīng)驗之談,容易理解。
1.2.概述
Symbian游戲是運行在手機(jī)上的游戲,它不能干擾手機(jī)正常
的通訊功能,對操作系統(tǒng)和其它應(yīng)用程序必須友善。而在首次編寫
SymbianC++游戲時,我遇到了無數(shù)奇怪的問題,其中大部分問題出
在內(nèi)存極端不足,打開太多應(yīng)用程序,屏幕保護(hù)探出,接到短信、電
話等特殊情況下。
其實如果養(yǎng)成嚴(yán)謹(jǐn)?shù)拇a風(fēng)格,進(jìn)行足夠的錯誤處理,大部
分問題本可以避免。為了解決它們,我很是花了一番功夫,所以在此
把我的一些教訓(xùn)、經(jīng)驗寫出,希望大家能避免犯同樣的錯誤。
如果你不是業(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ò)、資費和私人數(shù)據(jù)等
都有一定的要求。如果想了解SymbianSigned認(rèn)證的詳細(xì)內(nèi)容,可
以去它們的網(wǎng)站下載白皮書。
1.3.異常處理
雖然我們都知道任何一個new(ELeave)或者帶有L后綴的
函數(shù)都可能拋出異常,但是很多業(yè)余的愛好者還是會忽視Symbian
C++中異常處理的重要性。雖然有些函數(shù)只有在極其罕見的情況下才
會拋出異常。但不是危言聳聽,如果你不寫代碼捕捉并處理它們,應(yīng)
用程序就會遇到〃系統(tǒng)錯誤〃。
普通C++使用throw拋出異常。異常拋出后,棧會不停回滾,
直到遇到最近一層catch為止。SymbianC++中的異常處理不使用
try-catch和throw。但是它的處理機(jī)制和標(biāo)準(zhǔn)C++很是類似,區(qū)別
僅僅是它只能拋出一個整數(shù)錯誤碼,而不是一個任意對象。
我將從異常的拋出、捕捉、處理三方面講解這部分內(nèi)容。
1.3.1.拋出異常
SymbianC++中,有下面兒種情況下會拋出異常:
使用靜態(tài)函數(shù)User::Leave拋出異常。這個函數(shù)就是最基本的異常
產(chǎn)生函數(shù)。下面講的其它拋出方式都可以轉(zhuǎn)化為User::Leaveo
使用靜態(tài)函數(shù)User::LeavelfError把錯誤碼轉(zhuǎn)化為異常。有些函
數(shù)比如CFbsBitmap::Create()有一個Tint的返回值。如果遇到錯
誤,這些函數(shù)就會返回非KErrNone的錯誤值。此時,可以使用
LeavelfError把這個返回值轉(zhuǎn)化為異常。比如:
User::LeavelfError(bmp->Create(iSize,EColor4k);其實
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ī)范比你想象的要重要。因為它給其他程序員一個
暗示,提示他們對這些函數(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());//保護(hù)執(zhí)行
SomeDangerousFuncL()函數(shù)
if(errorCode!=KErrNone)
I
//捕捉到了一個異常,在這里添加處理異常的代碼
)
類似的TRAPD省去了你聲明一個局部變量的麻煩。頭兩行代碼可以簡
寫成:
TRAPD(errorCode,SomeDangerousFuncL());
1.3.3.處理異常
對于不同的異常當(dāng)然有不同的處理方法(廢話:-))。我們
以最常見的捕獲到代表內(nèi)存不足的KErrNoMemory異常為例講解。
注意在Container,AppUi等類的構(gòu)造過程中,你不需要加入對內(nèi)存
不足的保護(hù)。因為這一切系統(tǒng)已經(jīng)為你做好了。系統(tǒng)會彈出一個對話
框報告內(nèi)存不足。根據(jù)你的操作系統(tǒng)版本不同,這可能是中文的,也
可能是英文或者其它語言的。如果你不信,可以在AppUi或者
Container的ConstructL中寫一行User::Leave(KErrNoMemory)試試
看。我試驗的結(jié)果如下:
除了上面說的特殊情況,你可以簡單的彈出一個對話框,告
訴用戶沒有足夠的內(nèi)存運行程序,并且安全的關(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::Panic(_L(,zSomeothererror."),error);
)
)
其中noMemoryDlg是直接或者間接在Container的ConstructL中創(chuàng)
建的:
//inheaderfile
CAknQueryDialog*m_noMemoryDlg;
//somewhereinConstructL
TBuf<128>errMsg;
_LIT(formater,“Notenoughmemory.Pleaseclosesome
applications.,z);
errMsg.Copy(formater);
m_noMemoryDlg=new(ELeave)CAknQueryDialog(errMsg,
CAknQueryDialog::EErrorTone);
當(dāng)然,你也可以制作一個精美的圖片來報告內(nèi)存不足,等待
用戶按任意鍵再退出。不過載入這個圖片也可能會失敗,所以至少在
這個圖片成功載入之前,你還是需要系統(tǒng)對話框來報告的。
值得一提的是,你不一定需要退出程序,或者你可以稍后重
試申請內(nèi)存,幸運的話,沒準(zhǔn)第二次就能成功。這是因為Symbian系
統(tǒng)會在內(nèi)存不足時咱動關(guān)閉一些應(yīng)用程序。我覺得這是Symbian系統(tǒng)
一個比較奇怪的設(shè)計。通常應(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:AddYourcommandhandlingcodehere
default:
break;
)
)
坦白說我沒有嘗試過重試申請內(nèi)存這個辦法,不過我想是可行的。
1.3.4.?;貪L和對象的安全析構(gòu)
上面說到在遇到某些異常時,你可以選擇彈出對話框并且結(jié)
束程序,其實這會比你想象的要困難一些。因為C++可不像Java那
樣有托管堆進(jìn)行垃圾收集。不過好在C++棧會自動回滾,棧上的對象
會被銷毀。如果你此時調(diào)用CAknAppUi::RunAppShutter()結(jié)束程序,
那么AppUi,Container的析構(gòu)函數(shù)會依次被調(diào)用,引起你自己創(chuàng)建
對象的析構(gòu)函數(shù)也依次被調(diào)用。那么堆上的對象也要被銷毀??墒牵?/p>
請記住,異常隨時隨處可能發(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()
{
deletem_pBuf;
)
voidRebuildBufLO
{
m_pBuf=new(ELeave)TText8[256];
)
private:
BadExample();
"BadExample()
deletem_pBuf;
deletem_pBuf2;
)
voidConstructL()
(
m_pBuf=new(ELeave)TText8[256];
m_pBuf2=new(ELeave)TText8[256];
)
);
假設(shè)我們在AppUi的ConstructL中使用BadExamp在::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);
seif->ConstructL();
CleanupStack::Pop();
returnself;
)
但是注意,此處還有一個微妙的內(nèi)存泄露。仔細(xì)看看
Cleanupstack::PushL()的聲明:
IMPORT_CstaticvoidPushL(TAny*aPtr);
IMP0RT_CstaticvoidPushL(CBase*aPtr);
IMP0RT_CstaticvoidPushL(TCleanupItemanItem);
如果傳入的指針是CBase指針,那么CBase的虛析構(gòu)函數(shù)(virtual
~CBase())就能保證對象在銷毀時正確的調(diào)用析構(gòu)函數(shù)??墒潜纠?/p>
中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。因為標(biāo)準(zhǔn)C++規(guī)
定,delete一個空指針不做任何操作。不過實際上,如果對象從CBase
派生,這一步是沒有必要的,因為CBase能保證派生類的成員變量在
構(gòu)造時自動清零。
最后,動態(tài)的使用DeleteBuf和RebuildBufL是不安全的。如果你先
用DeleteBuf刪除了這個對象,那么m_pBuf就是一個壞指針。可是
緊接著的RebuildBufL可能會失敗。此時如果析構(gòu)函數(shù)被調(diào)用,還是
會產(chǎn)生delete無效指針的錯誤。正確的做法是,在DeleteBuf中,
把m_pBuf設(shè)為NULLo
總結(jié)上面說到的幾點,完整的安全的代碼是:
classBadExample:publicCBase
(
protected:
TText8*m_pBuf;
TText8*m_pBuf2;
public:
staticBadExample*NewLO
{
BadExample*self=new(ELeave)BadExample();
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop();
returnself;
voidDeleteBuf()
deletem_pBuf;
m_pBuf=NULL;
}
voidRebuildBufLO
{
m_pBuf=new(ELeave)TText8[256];
)
private:
"BadExample()
(
deletem_pBuf;
deletem_pBuf2;
)
voidConstructL()
(
m_pBuf=new(ELeave)TText8[256];
m_pBuf2=new(ELeave)TText8[256];
);
1.4.安全的圖像引擎
SymbianC++游戲的2D圖像顯示部分一般由下面兒個類組
成:
圖像-封裝了一個CWsBitmap。是基本的圖片資源。支持圖像之
間的各種貼圖和混合操作。
雙緩沖-一個和屏幕分辨率、色深相等的圖像。
直接寫屏支持-復(fù)合一個CDirectScreenAccess對象,實現(xiàn)
MDirectScreenAccess接口。負(fù)責(zé)直接寫屏的安全處理。比如來電、
屏保時適時的停止和開啟直接寫屏與游戲邏輯。
繪圖類-負(fù)責(zé)在圖像中繪圖。它不是對Gc的封裝,而是通過直接
修改圖像內(nèi)存區(qū)進(jìn)行繪圖。
位圖字體類-使用預(yù)先創(chuàng)建的位圖資源寫字。如下圖就是一個預(yù)
先創(chuàng)建的位圖資源。優(yōu)點是速度快,缺點是無法支持大字符集合,比
如中文。
字體緩沖區(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游戲最主要的畫面操作。為了實現(xiàn)快速的貼圖,
或者實現(xiàn)某種混合效果,就不能再使用CFbsBitGc的BitBlt或者
BitBltMasked進(jìn)行貼圖,而必須自己得到圖片的內(nèi)存地址,直接讀
寫其中的數(shù)據(jù)。在讀寫圖片內(nèi)存地址的過程中,有兒點需要加以注意。
首先,只有當(dāng)源圖片和目標(biāo)圖片色深相等時,才更容易進(jìn)行貼圖操作。
所以,再載入圖片的過程中,我習(xí)慣把非4k色的圖片轉(zhuǎn)化為4k色。
之所以選擇4k色是因為它也是后臺緩沖區(qū)的色深。下面的代碼通過
轉(zhuǎn)換可以保證iImage是妹色的圖像。
//Makesurethatwehavea4KcolordepthimageiniImage
if(i!mage->DisplayMode()!=EColor4K)
{
//Create4kcolorimage
CFbsBitmap*image=new(ELeave)CWsBitmap();
CleanupStack::PushL(image);
User::LeaveIfError(image->Create(iSize,EColor4K));
//Createdevice
CFbsBitmapDevice*device=CFbsBitmapDevice::NewL(image);
CleanupStack::PushL(device);
CFbsBitGc*gc;
User::LeavelfError(device->CreateContext(gc));
CleanupStack::PushL(gc);
//Bitbittonewcolordepth
gc->BitBlt(TPoint(0,0),iImage);
//Destroycontextanddevice;
CleanupStack::PopAndDestroy();//gc
CleanupStack::PopAndDestroy();//device
CleanupStack::Pop();//image
deleteiImage;
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,1.0),這兩個函數(shù)是私有的。我們必
須通過TBitmapUtil鎖定內(nèi)存。但是不一定必須使用TBitmapUtil的
SetPixel和GetPixel函數(shù)進(jìn)行位操作。下面是最基本的沒有關(guān)鍵色
和Alpha通道的簡單貼圖代碼。
voidCimage::RenderToBitmapL(CFbsBitmap*aBmp,TPointaPos,
constTRect&aRect)
I
//在此計算貼圖目標(biāo)矩形區(qū)域
//代碼略去
//沒有關(guān)鍵色和蒙板的最簡單、最快情況
if(!iKey&&iMask==NULL)
(
//鎖定
TBitmapUtilbmpUtill(ImageLO);
TBitmapUtilbmpUtil2(aBmp);
bmpUtill.Begin(TPoint(0,0));
bmpUtil2.Begin(TPoint(0,0),bmpUtill);
//獲取首地址
TUintl6*addr2=(TUintl6*)ImageL()->DataAddress();//
sourceimage
TUintl6*addr=(TUintl6*)aBmp->DataAddress();//target
bmp
Tintline=aBmp->ScanLineLength(
aBmp->SizeInPixels().iWidth,
EColor4K)/2;
Tintline2=iImage->ScanLineLength(//1inelengthin
16bitword
iImage->SizeInPixels().iWidth,
EColor4K)/2;
//計算掃描持續(xù)量和跳躍量
Tintjump=line-rectw;
Tintlasting2=rectw;
Tintjump2=1ine2-lasting2;
//獲取貼圖首地址
TUintl6*p=addr+fromY*aBmp->SizeInPixels().iWidth+
fromX;
TUintl6*p2=addr2+line2*recty+rectx;
//Thefirstpixeloutofinterest
TUintl6*p2end=p2+line2*(toY-fromY-1)+lasting2
+jump2;
//開始掃描
while(p2!=p2end)
//開始一個掃描行
TUintl6*p2endline=p2+lasting2;
while(p2!=p2endline)
(
//復(fù)制一個像素
*p=*p2;
//移動到下一個像素
p++;p2++;
)
//跳到下一行
P+=jump;p2+=jump2;
)
//解鎖
bmpUtil2.End();
bmpUtill.End();
return;
}
//其它情況。有關(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ū)動游
戲邏輯的計時器(一般是個CPeriodic對象),停止聲音模塊(一般
是一個CActive任務(wù))就可以了。
值得費些力氣的是Restart函數(shù),它并不是在應(yīng)用程序回到前臺,并
且可以進(jìn)行全屏直接寫屏?xí)r才被回調(diào)。所以不能在此時武斷的恢復(fù)游
戲邏輯,開始游戲。
首先,你要調(diào)用CDirectScreenAccess::StartL()恢復(fù)直接寫屏。
但是必須給這個函數(shù)加上TRAP保護(hù)。因為它很可能拋出
KErrNotReady異常。如果遇到這個異常,那你就直接返回好了,因
為直接寫屏此時并不能開始。接下來你需要檢查一下繪圖區(qū)域,看是
否整個屏幕都可以被使用。如果不是,那也無需啟動游戲邏輯,只需
要用最后保留的后臺緩沖區(qū)的內(nèi)容更新直接寫屏區(qū)域即可。第三種情
況,如果直接寫屏成功啟動,并且整個屏幕都可以被繪制,才啟動游
戲邏輯,啟動聲音等其它模塊。
完整的代碼如下:
void
CEngine::AbortNow(RDirectScreenAccess::TTerminationReasons
/*aReason*/)
//Canceltimeranddisplay
if(iGameTimer->IsActive())
iGameTimer->CancelTimer();
if(!iGameWorldPaused)
{
iGameWor1dPaused=ETrue;
iGameWorld->PauseGame();//Pauseaudiostreametc.
iPaused=ETrue;
void
CEngine::Restart(RDirectScreenAccess::TTerminationReasons
/*aReason*/)
I
TRAPD(error,SetupDirectScreenAccessL());
switch(error)
(
caseKErrNone:
break;
caseKErrNotReady:
if(iDirectScreenAccess->IsActive())
iDirectScreenAccess->Cancel();
if(iGameTimer->IsActive())
iGameTimer->CancelTimer();
if(!iGameWor1dPaused)
(
iGameWorldPaused=ETrue;
iGameWorld->PauseGame();
return;
default:
User::Panic(_L(''SetupDSAError"),error);
)
if(iPaused)
(
if(iGameDawingArea==iRegion->BoundingRect())
(
iPaused=EFalse;
if(!iGameTimer->IsActive())
{
iGameWorldPaused=EFalse;
iGameWorld->ResumeGame();
iGameTimer->Restart();
)
)
else
PauseFrame();
else
if(!iGameTimer->IsActive())
{
iGameTimer->Restart();
)
)
)
voidCEngine::SetupDirectScreenAccessL()
{
//InitialiseDSA
iDirectScreenAccess->StartL();
//Getgraphicscontextforit
iGc=iDirectScreenAccess->Gc();
//GetregionthatDSAcandrawin
iRegion=iDirectScreenAccess->DrawingRegion();
//Setthedisplaytocliptothisregion
iGc->SetClippingRegion(iRegion);
voidCEngine::PauseFrame()
//Forcescreenupdate:thisisrequiredforWINS,butmay
//notbeforallhardware:
iDirectScreenAccess->ScreenDevice()->Update();
//anddrawfromunchangedoffscreenbitmap
iGc->BitBlt(TPoint(0,0),
&(iDoubleBufferedArea->GetDoub1eBufferedAreaBitmap()));
iClient.Flush();
)
);
1.5.聲音處理
我的引擎中使用CMdaAudioOutputStream和
MMdaAudioOutputStreamCallback完成聲音播放功能。它主要有三個
類組成:
CAudioStreamPlayero它復(fù)合CMdaAudioOutputStream,繼承
CActive,實現(xiàn)MMdaAudioOutputStreamCallback接口。我們需要小
心的維持緩沖區(qū)的大小以獲得低延遲播放。CActive不斷的建立新的
任務(wù),在RunL函數(shù)中估算緩沖區(qū)中的剩余數(shù)據(jù),向其中追加適當(dāng)?shù)?/p>
數(shù)據(jù),維持緩沖區(qū)的預(yù)期大小。
CSimpleMixero它實現(xiàn)CAudioGenerator接口。因為
CMdaAudioOutputStream是一個單一的流式播放器,所以需要寫一個
混音器進(jìn)行波形混合。這里波形混合就是簡單的數(shù)據(jù)相加?;煲羝饔?/p>
許多的聲道(channel)。每個channel記錄了其中的CAudio指針和
當(dāng)前播放位置。
CAudioo包含一個音頻緩沖區(qū)。對每個聲音文件,我們還需要一個
類把它載入到內(nèi)存緩沖區(qū)中。
我不會在此講解如何實現(xiàn)音頻播放,那需要單獨的一篇文
章。如果你也使用這種方法實現(xiàn)聲音播放,我只想在此和大家討論兩
個問題。
需要學(xué)習(xí)聲音基礎(chǔ)的話,可以參考
/article.php?id_article=113o(可惜我當(dāng)時學(xué)習(xí)聲
音時那篇文章和代碼找不到了)
1.5.1.聲音的關(guān)閉和開啟
因為整個音頻系統(tǒng)是一個拉的結(jié)構(gòu),音頻流從混音器那里拉
數(shù)據(jù),混音器從音頻緩沖區(qū)中拉數(shù)據(jù)。所以,只要把
CMdaAudioOutputStream和寫數(shù)據(jù)的CActive對象delete掉,聲音
播放就全部停止了。在我的實現(xiàn)中,也就是delete
CAudioStreamPlayer對象即可。再想要開啟聲音,只需要重新創(chuàng)建
這個對象。
這個實現(xiàn)的好處是程序的其它部分不需要保存聲音是否開啟這個狀
態(tài)。因為CAudio和CS租pleMixer對象是存在的,CAudio就可以把
自己插入到Mixer的channel中,覺得自己好像在播放一樣。其實因
為CAudioStreamPlayer根本沒有從Mixer向外拉數(shù)據(jù),聲音設(shè)備是
完全停止的。
但是在恢復(fù)聲音播放時有一點需要注意,恢復(fù)前需要清空混
音器中的聲音數(shù)據(jù)。因為經(jīng)過了長時間的運行,混音器中的各個
channel中已經(jīng)塞滿了各種聲音。如果此時突然打開,會傳出各種延
遲了的雜音。
1.5.2.特殊錯誤處理
MMdaAudioOutputStreamCalIback接口中的兒個回調(diào)函數(shù)
MaoscOpenComplete>MaoscBufferCopied和MaoscP1ayComp1ete都
有一個錯誤碼參數(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)在我的實現(xiàn)中就是指先delete再NewL創(chuàng)建
CAudioStreamPlayer對象。
這三個錯誤的處理代碼如下:
//AudiostreamAPI
溫馨提示
- 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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年度綠色環(huán)保大棚生產(chǎn)項目投資合作協(xié)議3篇
- 二零二五版新能源汽車電池回收利用協(xié)議3篇
- 二零二五版企業(yè)臨時客戶服務(wù)團(tuán)隊聘用協(xié)議3篇
- 二零二五年接送機(jī)服務(wù)及接送站及行李打包及寄存合同3篇
- 專用海洋環(huán)境風(fēng)力發(fā)電機(jī)買賣協(xié)議版B版
- 2025年度煤炭企業(yè)兼并重組項目合作合同范本4篇
- 二手房購買誠意金協(xié)議模板版A版
- 二零二五版攪拌站工程勞務(wù)分包合同標(biāo)準(zhǔn)文本12篇
- 二零二五版房屋買賣合同中的公攤面積及分?jǐn)偡绞秸f明3篇
- 2025年度文化演出場所租賃管理合同4篇
- 春節(jié)行車安全常識普及
- 電機(jī)維護(hù)保養(yǎng)專題培訓(xùn)課件
- 汽車租賃行業(yè)利潤分析
- 春節(jié)拜年的由來習(xí)俗來歷故事
- 2021火災(zāi)高危單位消防安全評估導(dǎo)則
- 佛山市服務(wù)業(yè)發(fā)展五年規(guī)劃(2021-2025年)
- 房屋拆除工程監(jiān)理規(guī)劃
- 醫(yī)院保安服務(wù)方案(技術(shù)方案)
- 高效能人士的七個習(xí)慣:實踐應(yīng)用課程:高級版
- 小數(shù)加減法計算題100道
- 通信電子線路(哈爾濱工程大學(xué))智慧樹知到課后章節(jié)答案2023年下哈爾濱工程大學(xué)
評論
0/150
提交評論