移動行業(yè)信息化-編寫安全的SymbianC游戲代碼_第1頁
移動行業(yè)信息化-編寫安全的SymbianC游戲代碼_第2頁
移動行業(yè)信息化-編寫安全的SymbianC游戲代碼_第3頁
移動行業(yè)信息化-編寫安全的SymbianC游戲代碼_第4頁
移動行業(yè)信息化-編寫安全的SymbianC游戲代碼_第5頁
已閱讀5頁,還剩26頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論