游戏开发基础:游戏开发基础(6)



节 有关声音
声音是空气系列振荡称为声波般可以用 2维波形图来表示数字音频是指使用某种设备将声波记录下来并保存为种数字化文件播放相应文件就可以产生某种声音效果数字音频音质随着采样频率及所使用位数区别而有很大差异因此了解所使用音频文件格式有关标准是很有必要例如CD中音频是16位采样频率达到44.1MHz立体声数字音频
在所有声音文件格式中WAV是最普遍这是Windows平台上最常见格式由微软公司创造支持8位和16位音质、多样本、对立体声和单声道音频均可播放它还支持多种音频压缩算法
要在游戏中取得好声音效果例如使用3D音效可以有两种思路方法来实现:是使用工具软件Software对声音文件进行处理生成播放效果足够好文件然后在游戏中直接将这样文件播放显然这样比较简单但是不灵活如果需要音效随着游戏场景变化而不断改变且不受所具有声音文件数量限制就需要进行实时混音了


第 2节DirectSound结构
DirectSound功能模块包括播放、声音缓冲区、 3维音效、音频抓获、属性集等
DirectSound playback建构于IDirectSound COM接口的上IDirectSoundBufferIDirectSound3DBuffer和
IDirectSound3DListener接口则用以实现对声音缓冲区和 3维音效操作
DirectSound capture建构于IDirectSoundCapture和IDirectSoundCaptureBuffer COM接口的上
其它COM接口如IKsPropertySet使应用能够从声卡扩展功能中最大地受益
最后IDirectSoundNoty接口用于在播放或音频抓获达到定地方时向产生个事件


第 3节 播放功能概述
DirectSound缓冲区对象表示个包含声音数据缓冲区这些数据以PCM格式被存储该对象不仅可以用于开始、停止或暂停声音播放还能够设置声音数据中诸如频率和格式等属性
缓冲区分为主缓冲区和副缓冲区主缓冲区中是听者将要听到音频信号般是将副缓冲区中信号混音后结果而副缓冲区中存放着许多单独声音信号可以直接播放要混音循环播放主缓冲区由DirectSound自动创建而副缓冲区需由应用来创建DirectSound将副缓冲区中声音混合后存入主缓冲区再输出到相应播放设备
DirectSound中没有解析声音文件功能需要您自己在应用中将区别格式声音信号改变过来(PCM)
缓冲区可以在主板RAM、波表存储器、DMA通道或虚拟存储器中
多个应用可以用同声音设备来创建DirectSound对象当输入焦点在应用中发生变化时音频输出将自动在各个应用流的间切换于是应用不用在输入焦点改变中反复地播放和停止它们缓冲区

通过IDirectSoundNoty接口当播放到了个用户指定地方或播放结束时DirectSound将动态地通知拥护这事件


第 4节 音频抓获概述
DirectSoundCapture对象可以查询音频抓获设备性能并为从输入源抓获音频而创建缓冲区
其实在Win32中早已经有了抓获音频功能而目前(版本5)DirectSoundCapture和只比较并没有什么新功能不过DirectSoundCapture API使您能够编写使用相同接口播放和音频抓获而且这也为将来可能出现API改进提供了原始模型使您可以从中受益
DirectSoundCapture还能够抓获压缩格式音频
DirectSoundCaptureBuffer对象表示个用于抓获音频缓冲区它可以循环利用也就是说当输入指针达到缓冲区最后时它会回到开始地方
DirectSoundCaptureBuffer对象各种方式使您能够设定缓冲区属性、开始或停止操作、锁定某部分存储器(这样就可以安全地将这些数据保存或用于其它目)
和播放类似IDirectSoundNoty接口使在输入指针到达定地方时通知用户


第 5节
对于些简单操作可以使用缺省首选设备不过在游戏制作中我们可能还是需要知道些特定声音设备于是您应该先列举出可用声音设备
在此的前您需要先设定个回收在每次DirectSound发现新设备后中您可以做任何事情但您必须将它定义得和DSEnumCallback形式相同如果希望列举继续应返回真否则返回假
下面例程来自光盘Example目录下Dsenum.c文件它列举可用设备并在个列表框中增加条相应信息首先是他回收:

BOOL CALLBACK DSEnumProc(LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID lpContext )
{
HWND hCombo = *(HWND *)lpContext;
LPGUID lpTemp = NULL;

( lpGUID != NULL )
{
(( lpTemp = LocalAlloc( LPTR, (GUID))) NULL )
( TRUE );

memcpy( lpTemp, lpGUID, (GUID));
}

ComboBox_AddString( hCombo, lpszDesc );

ComboBox_SetItemData( hCombo,
ComboBox_FindString( hCombo, 0, lpszDesc ), lpTemp );
( TRUE );
}

当包含了列表框对话框被化后列举开始:

(DirectSoundEnumerate((LPDSENUMCALLBACK)DSEnumProc, &hCombo) != DS_OK )
{
EndDialog( hDlg, TRUE );
( TRUE );
}

创建DirectSound对象最简单思路方法是使用DirectSoundCreate其中个参数为相应设备全局独有标志符(GUID)您可以通过列举声音设备得到GUID或使用NULL来为缺省设备创建对象

LPDIRECTSOUND lpDirectSound;
HRESULT hr;
hr = DirectSoundCreate(NULL, &lpDirectSound, NULL));

创建DirectSound对象后应设置合作层这是为了确定各个DirectSound应用被允许操作声音设备范围防止它们在时间或通过方式操作设备
所使用方式为IDirectSound::SetCooperativeLevel这里hwnd参数是应用窗口句柄:



HRESULT hr = lpDirectSound->lpVtbl->SetCooperativeLevel( lpDirectSound, hwnd, DSSCL_NORMAL);

这里确定合作层为normal这样使用声卡应用可以顺序地进行切换合作层包括Normal、Priority、Exclusive和Write-primary级别依次增加
正如在前面提到过DirectSound可以充分发挥硬件增强功能因此它需要先设法了解设备特性我们可以通过IDirectSound::GetCaps方式来达到这个要求如下所示:

DSCAPS dscaps;

dscaps.dwSize = (DSCAPS);
HRESULT hr = lpDirectSound->lpVtbl->GetCaps(lpDirectSound, &dscaps);

DSCAPS结构接收有关声音设备性能和资源信息注意化该结构中dwSize成员是它的前所必须
除此的外您还可以查询和设定扬声器设置以及整理声音存储器使尽量获得最大备用空间


第 6节 如何播放
化完成后DirectSound将自动创建主缓冲区用于混音并传送至输出设备而副缓冲区则需要您自己来创建了
下面例程演示了用IDirectSound::CreateSoundBuffer方式创建个基本副缓冲区:

BOOL AppCreateBasicBuffer( LPDIRECTSOUND lpDirectSound, LPDIRECTSOUNDBUFFER *lplpDsb)
{
PCMWAVEFORMAT pcmwf;
DSBUFFERDESC dsbdesc;
HRESULT hr;
// 设定声波格式结构
mem(&pcmwf, 0, (PCMWAVEFORMAT));
pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM;
pcmwf.wf.nChannels = 2;
pcmwf.wf.nSamplesPerSec = 22050;
pcmwf.wf.nBlockAlign = 4;
pcmwf.wf.nAvgBytesPerSec =
pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign;

pcmwf.wBitsPerSample = 16;
// 设置DSBUFFERDESC结构用以设定缓冲区控制选项
mem(&dsbdesc, 0, (DSBUFFERDESC));
dsbdesc.dwSize = (DSBUFFERDESC);
// 要求缺省控制
dsbdesc.dwFlags = DSBCAPS_CTRLDEFAULT;
// 3秒缓冲区
dsbdesc.dwBufferBytes = 3 * pcmwf.wf.nAvgBytesPerSec;
dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf;
// 创建缓冲区
hr = lpDirectSound->lpVtbl->CreateSoundBuffer(lpDirectSound, &dsbdesc, lplpDsb, NULL);
(DS_OK hr) {
// 成功获得接口在*lplpDsb当中
TRUE;
} {
// 失败
*lplpDsb = NULL;
FALSE;
}
}

您必须设定缓冲区控制选项这是使用DSBUFFERDESC结构中dwFlags成员具体细节请参见DirectX 5帮助
副缓冲区不支持混音等特效因此您需要能够直接操作主缓冲区不过当您获权写主缓冲区时其它特性将失去作用从而硬件加速混音失效所以大部分应用几少直接操作主缓冲区
如果要求操作主缓冲区可以在IDirectSound::CreateSoundBuffer方式时设定DSBUFFERDESC结构中DSBCAPS_PRIMARYBUFFER标志符而且合作层必须是Write-primary
下面例程演示了如何得到对主缓冲区写操作能力:

BOOL AppCreateWritePrimaryBuffer( LPDIRECTSOUND lpDirectSound, LPDIRECTSOUNDBUFFER *lplpDsb, LPDWORD lpdwBufferSize, HWND hwnd)
{
DSBUFFERDESC dsbdesc;
DSBCAPS dsbcaps;
HRESULT hr;
// 设置声波格式结构
mem(&pcmwf, 0, (PCMWAVEFORMAT));
pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM;
pcmwf.wf.nChannels = 2;
pcmwf.wf.nSamplesPerSec = 22050;
pcmwf.wf.nBlockAlign = 4;
pcmwf.wf.nAvgBytesPerSec = pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign;
pcmwf.wBitsPerSample = 16;
// 设置DSBUFFERDESC结构
mem(&lplpDsb, 0, (DSBUFFERDESC));
dsbdesc.dwSize = (DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
// 缓冲区大小由声音硬件决定
dsbdesc.dwBufferBytes = 0;
dsbdesc.lpwfxFormat = NULL; // 对主缓冲区必须设为NULL

// 获得write-primary合作层

hr = lpDirectSound->lpVtbl->SetCooperativeLevel(lpDirectSound, hwnd, DSSCL_WRITEPRIMARY);
(DS_OK hr) {
// 成功试图创建缓冲区
hr = lpDirectSound->lpVtbl->CreateSoundBuffer(lpDirectSound, &dsbdesc, lplpDsb, NULL);
(DS_OK hr) {
// 成功设定主缓冲区为desired格式
hr = (*lplpDsb)->lpVtbl->SetFormat(*lplpDsb, &pcmwf);
(DS_OK hr) {

// 如果希望得知缓冲区大小GetCaps
dsbcaps.dwSize = (DSBCAPS);
(*lplpDsb)->lpVtbl->GetCaps(*lplpDsb, &dsbcaps);
*lpdwBufferSize = dsbcaps.dwBufferBytes;
TRUE;
}
}
}
// 设定合作层失败
// 创建缓冲区或设定结构
*lplpDsb = NULL;
*lpdwBufferSize = 0;
FALSE;
}

播放段声音过程包括以下 4个步骤:
1 锁定(IDirectSoundBuffer::Lock)副缓冲区部分由您设定偏移量决定下步写操作起始点;
2 写数据;
3 解锁(IDirectSoundBuffer::Unlock);
4 将声音传送给主缓冲区并由那里输出(IDirectSoundBuffer::Play)
下面C向缓冲区中写入数据由dwOff指定开始时偏移量:

BOOL AppWriteDataToBuffer( LPDIRECTSOUNDBUFFER lpDsb, // DirectSound缓冲区


DWORD dwOff, // 自己写标记位置
LPBYTE lpbSoundData, // 数据起点
DWORD dwSoundBytes) // 拷贝块大小
{
LPVOID lpvPtr1;
DWORD dwBytes1;
LPVOID lpvPtr2;
DWORD dwBytes2;
HRESULT hr;
// 得到被写块地址
hr = lpDsb->lpVtbl->Lock(lpDsb, dwOff, dwSoundBytes, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);

// 如果返回DSERR_BUFFERLOST释放并重试锁定
(DSERR_BUFFERLOST hr) {
lpDsb->lpVtbl->Restore(lpDsb);
hr = lpDsb->lpVtbl->Lock(lpDsb, dwOff, dwSoundBytes, &lpvPtr1, &dwAudio1, &lpvPtr2, &dwAudio2, 0);
}
(DS_OK hr) {
// 写到指针
CopyMemory(lpvPtr1, lpbSoundData, dwBytes1);
(NULL != lpvPtr2) {
CopyMemory(lpvPtr2, lpbSoundData+dwBytes1, dwBytes2);
}
// 释放
hr = lpDsb->lpVtbl->Unlock(lpDsb, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
(DS_OK hr) {
// 成功
TRUE;
}
}
// 某步骤失败
FALSE;
}

Tags:  网页游戏开发 网络游戏开发 游戏开发 游戏开发基础

延伸阅读

最新评论

发表评论