DirectSound详细介绍

目录:
  音效的表现
  Play Sound using PlaySound()
  使用DirectSound
  相关事宜
  末语
文档内容:


□ 音效的表现


  WINDOW下音效档的使用,大抵上就是使用WAV档,通常音效只是一小段,不会超过数秒的声音档,当然你可以疯狂一点,将整个WAV档当成音乐来播放,如同前面提到的,与其要这样做,倒不如将WAV档弄成CD音轨,在WINDOW下播放一片CD片,也是如同想像中的简单,这边我们不谈这部份,专心探讨WAV档的使用时机与程式作法的差异。我会讨论不同的作法,以及解释为什麽有些人宁愿舍简就繁,抱头猛钻DirectSound(当然这不一定是正确的)。


□ Play Sound using PlaySound()


  是的,在WINDOW API的支援下,播放小段WAV档的最佳方案就是使用PlaySound()函示,他简单到什麽地步呢?比方我要播放sound.wav档,我只要这麽做就好:


PlaySound("sound.wav",NULL,SND_FILENAME|SND_ASYNC);


最後的叁数SND_ASYNC 代表音效开始拨放以後,不等待,直接从函示返回,当然其他的叁数诸如SND_LOOP可以让音效重复播放,直到下次呼叫PlaySound()为止。


这边我们必须探讨PlaySound()的使用时机,以及其限制,否则我们很难想像,为什麽还需要DirectSound的存在。PlaySound()的第一个限制就是WAV档案的大小,事实上是有限制的,当WAV档案太大的话,PlaySound()无法处理。第二个限制就是混音的功能,PlaySound()没办法帮上任何一点忙,比方说,在一个场景里面,同时有一只小鸡,一只鸭,他们同时在叫,这时候我需要同时表现这两种声音,我这麽做:


PlaySound("chicken.wav",NULL,SND_FILENAME|SND_ASYNC);


PlaySound("duck.wav",NULL,SND_FILENAME|SND_ASYNC);


不行,在第一个PlaySound()呼叫的时候,他确实正要播放鸡叫的声音,但是接着马上又呼叫第二个函示,结果鸡叫的声音停止了,转而播放鸭子叫的声音,所以你永远只会听到鸭子的叫声。你会说,如果我不使用非同步作法呢?你会得到的结果是这样的:你先听完鸡叫的声音,然後鸡叫完以後,才听见鸭叫的声音,总之事情就是这个样子,PlaySound()好用,但是他的功能在这边完全派不上用场了。


在这边我们归纳PlaySound()使用的时机:凡是不使用到混音的功能,仅单纯播放片段WAV档者,使用之。换句话说,如果你的游戏同时间只会存在一种音效,PlaySound()将会是最好的选择,不必使用到DirectSound的功能。事实上我们看到国内的游戏大致上都可以用PlaySound()搞定,开门的声音,开宝箱的声音,战斗砍人的声音,实际上同时间只有一种音效播放中,用PlaySound()才是明智的选择。但是我真的见过某些程式,明明只播放单音的WAV档,却还煞有其事地使用复杂的DirectSound,我宁可相信他是为了练习,而不是因为不知道有PlaySound()可用而舍简就繁。


另外在VC的整合环境下,有些人使用PlaySound()遇到的问题都是Link Error,找不到PlaySound()的连结点,事实上这个函示包含在Winmm.lib里面,整合环境的专案内定值不包含这个函示库,要自行加入。


□ 使用DirectSound


  在程式需要混音的时候,DirectSound就显得相当有价值了,因为他会替你处理掉许多内部的动作,你所需要做的,就是用,用就对了,也不必管他内部在搞什麽鬼。关於DirectSound的内容,说真的也相当繁杂,我仅就应用的层面,提醒一下哪些时候该使用什麽方式,并举一些例子供叁考。


  在了解DirectSound的时候,最重要的就是区分音效Buffer的不同,其中分成两种 primary buffer (主要音效缓冲区)与 secondary buffer(次要音效缓冲区)。Primary buffer通常我们不会去管他,直接交给
DirectSound操作即可,只有在你需要完全控制混音的情况下,你才会需要动到primary buffer的内容,根据我的经验,这部份是不需要操心的。需要我们操心的部份是在 secondary buffer,这种buffer理论上可以产生不限制的个数,而primary buffer仅有一个,当然实际能产生多少 secondary buffer要看记忆体的多寡来决定。当我们同时播放secondary buffer的时候,其资料会流向 primary buffer,并且DirectSound会自动完成许多内部的动作,比方音效格式的转换,混音的支援等等。


  播放WAV的方式,也分成两种,第一种是小片段的音效,使用 static buffer,播放资料量极大的WAV档案,你需要用到的是 streaming buffer,这两种的差别有必要搞清楚,所谓static buffer就是静态,小量的音效,通常我们会让他一直常驻在记忆体里面,而不会感到心虚,毕竟所占的记忆体极小。Streaming buffer则是播放大的WAV档,采用分批播放的一种方式,所以程式中必须不停地写入各段的资料。他们使用的方式大概是这样的,如同前面所提到的阿鸡阿鸭WAV音效,档案小,又必须经常使用者,我们使用static buffer,而准备将WAV当音乐播放者(很疯狂),因为一首三分钟的WAV音乐,通常占用数十MB的空间,所以没有别的选择,必须使用streaming buffer。


  好了,疯狂的事情留给别人去做,我这边示范一下混音的方式,就如同星海争霸那种整个场景都是动物惨叫声的情况,我们也模拟一下,同时播放五种动物的叫声,当然他们是很和平相处的。在这边的程式范例,有些撷取自DirectX内附的SAMPLE,比方处理WAV格式的部份,我们可以在 SAMPLE里边找到WAVE.C这个档案直接套用。
许多函示呼叫,旗标用法,因为太庞杂了,所以请各位叁考DirectSound的文件,我不拟赘述,介绍三个函示如下:


BOOL InitDSound( HWND hwnd)
{
     HRESULT hr;


     // Create DirectSound.
     if ( FAILED( hr = DirectSoundCreate(NULL, &lpds, NULL ) ) )
         return FALSE;


     // Set cooperative level.
     if ( FAILED( hr = lpds->SetCooperativeLevel( hwnd, DSSCL_PRIORITY ) ) )
         return FALSE;


     return TRUE;
} // 初始化DirectSound


BOOL InitDSound( HWND hwnd) 仅简单地初始化DirectSound,事实上跟DirectDraw的初始化部份相当类似,所以感觉上都是基本的调调,没什麽好特别讨论的。接着介绍第二个函示:


LPDIRECTSOUNDBUFFER LoadStatic (LPDIRECTSOUND lpds,LPSTR lpzFileName)
{
     WAVEFORMATEX *pwfx; 
     HMMIO hmmio; 
     MMCKINFO mmckinfo; 
     MMCKINFO mmckinfoParent; 
     LPDIRECTSOUNDBUFFER lpdsbStatic=NULL;
     LPVOID lpvAudio1;
     DWORD dwBytes1;
     DSBUFFERDESC dsbdesc;
     UINT cbBytesRead;


     if ( WaveOpenFile( lpzFileName, &hmmio, &pwfx, &mmckinfoParent ) != 0 )
         return FALSE;


     if ( WaveStartDataRead( &hmmio, &mmckinfo, &mmckinfoParent ) != 0 )
         return FALSE;


     if ( lpdsbStatic == NULL )
     {
         memset( &dsbdesc, 0, sizeof( DSBUFFERDESC ) ); 
         dsbdesc.dwSize = sizeof( DSBUFFERDESC ); 
         dsbdesc.dwFlags = DSBCAPS_STATIC; 
         dsbdesc.dwBufferBytes = mmckinfo.cksize; 
         dsbdesc.lpwfxFormat = pwfx; 
         if ( FAILED( lpds->CreateSoundBuffer(&dsbdesc, &lpdsbStatic, NULL ) ) )
         {
             WaveCloseReadFile( &hmmio, &pwfx );
             return FALSE; 
         }
     }


     lpdsbStatic->Lock( 0, 0, &lpvAudio1, &dwBytes1, NULL, NULL, DSBLOCK_ENTIREBUFFER );
     WaveReadFile( hmmio, dwBytes1, ( BYTE * )lpvAudio1,&mmckinfo, &cbBytesRead );
     lpdsbStatic->Unlock( lpvAudio1, dwBytes1, NULL, 0 );
     WaveCloseReadFile( &hmmio, &pwfx );


     return lpdsbStatic;
}


LoadStatic() 主要功能载入WAV档,在这个函示里面,我们也使用到WAVE.C里面的函示,专门处理WAV档。
当我们传入WAV的档名以後,呼叫成功以後,会传回一个static secondary buffer的指标。这代表我们可以开始对音效做处理了,不用说,当然就是播放罗。底下的函示便是:


void PlayStatic(LPDIRECTSOUNDBUFFER lpdsbStatic)

     if ( lpdsbStatic == NULL ) return;


     lpdsbStatic->SetCurrentPosition(0);


     lpdsbStatic->Play(0,0,0); 
}


PlayStatic()仅简单地使用Play(),就完成播放的动作了,其他的事情不用我们操心,在这边比较复杂的倒是搞定WAVE的内容与格式,还好有WAVE.C的帮助,我们没有遭遇太大的难关。至於这三个函示怎麽使用呢?
底下做个简单的示范:


首先定义一个全域的变数lpds,供InitDSound()使用,这样的指标整个程式只有一个,所以定义成全域变数是很合理的作法。紧接着数数看你需要几个static buffer,以我们的例子来说,我需要五个,所以我定义的变数如下:


LPDIRECTSOUNDBUFFER lpdsb[5]; //配置五个音效指标


要替这五个指标做初始化的的工作,只要笨笨地呼叫LoadStatic()即可,作法大致相同:


// 替这五个指标作初始化的工作


lpdsb[0]=LoadStatic(lpds,"chicken.wav");


lpdsb[1]=LoadStatic(lpds,"duck.wav");


lpdsb[2]=LoadStatic(lpds,"horse.wav");


lpdsb[3]=LoadStatic(lpds,"pig.wav");


lpdsb[4]=LoadStatic(lpds,"dragon.wav");


到了这里,已经完成所有的任务了,这时候我们可以随时使用PlayStatic()来播放音效,因为我们要测试一下混音的效果,所以我故意连续Play五次:


PlayStatic(lpdsb[0]);


PlayStatic(lpdsb[1]);


PlayStatic(lpdsb[2]);


PlayStatic(lpdsb[3]);


PlayStatic(lpdsb[4]);


这下子没问题了,你要怎麽混音都随便你,执行完毕会有五种动物的声音同时呈现,真是壮观的「音面」呀,虽然「场面」上倒是不知道这些动物躲在哪里啦。


□ 相关事宜


  关於3D音效的作法,要配合3D的游戏比较具有真正的意义,不过我们也可以使用简单的方式让音效更具真实感,在上面说到的 static buffer,每一个都可以当成独立的物件看待,其中包含控制音效的音量,喇叭的左声道右声道,更重要的是,你设定其中一个音效的音量,不会影响到另外一个音效的音量,换句话说,他们独立作业就是了。一个简单的应用方式,如果你判断某物体距离你越来越远,则可以让那个物体的音量渐渐减低,制造远近距离的感觉。又有如战斗方式的程式,砍杀敌人的声音可以分成左右声道来表现,比方从左边杀过来,则让右边的音效消音,只让左边的喇叭发声,都是可行的办法。而程式要控制这些部份只要增加一个旗标即可,在dsbdesc.dwFlags 加上DSBCAPS_CTRLDEFAULT即可。如果忘了加入这个旗标,则所有设定音量,左右声道,音频的呼叫都会失败。


□ 末语


  看完这篇文章,音乐音效的处理大致上已经没有问题了,有问题的倒是要如何做出动人的音乐,这部份当然不是我们程式者的任务。你可以到我的网站取得此次介绍的类别与函示库,直接套用即可,另外我会写一个混音的小范例,让你实际听一下效果如何。


DirectSound


  
目录:
  关于DirectSound
  DirectSound设备
    枚举可以使用的声音设备
    创造DirectSound对象
    设置合作级
    检索硬件信息
    扬声器的设置
    压缩
  DirectSound缓冲
    静态缓冲和流缓冲
    创建辅助缓冲
    缓冲控制选项
    主缓冲的存取
    播放声音
    重放(PLAYBACK)的控制
    播放进度和可以被写的位置(Current Play and Write Positions)
    播放缓冲时的通知(PLAY BUFFER NOTIFITACION)
    混音(MIXING SOUND)
    自己的混音


文档内容:




(费话篇)
关于DirectSound




  DirectSound是DirectX API的音频(waveaudio)组件之一,它可以提供快速的混音、硬件加速功能,并且可以直接访问相关设备,当然,最主要的是它提供的功能与现有的(?将来的呢?)设备驱动程序保持兼容性。
DirectSound允许进行波型声音的捕获、重放,也可以通过控制硬件和相应的驱动来获得更多的服务。
DirectSound的优势当然和DirectX的其它组件一样——速度,它允许你最大效率的使用硬件,并拥有良好的兼容性(其实别的都好说,就要这两样就够了:P)。


使用DirectSound可以做到什么呢?


1、很方便的了解硬件能力,并且根据当前计算机硬件配置硬件来决定最好的解决问题的方法。


2、弥补驱动程序的不足——通过属性设置以便硬件能力可以完全发挥,即便是驱动程序没有很好的支持该功能。


3、短传输延迟时间的混音为了快速的响应流。


4、3-D声音


5、声音的捕获


DirectSound核心
DirectSound设备


  这部分描述了将怎样枚举可以使用的声音设备、为一设备创造DirectSound对象、使用对象的方法来设置合作级别、检验设备的能力、创造声音缓冲、配置系统扬声器和压缩数据。


枚举可以使用的声音设备


  一般的情况下,可能并不需要枚举可以使用的声音设备,使用缺省设备不会带来什么麻烦(相信用户-上帝:P的设置),但是如果你希望将程序做得更“面对对象”一些,给用户更多更好的选择(很多3D GAME都可以让用户来选择显卡,道理大同小异),那你就需要使用到枚举可供使用的声音设备了。
  枚举可供使用的声音设备首先要写一回调函数,在程序每找到一可供使用的声音设备时调用,在那个函数里你可以做任何事,并且它可以拥有任何的合法的名字,但是你必须作为DSEnumCallback声明它,回调函数必须返回一BOOL,TRUE则继续列表,FALSE就退出枚举过程了。
  和显示设备的枚举差不多,下面就是载自Dsenum.c的几段代码。由于本人认为使用得不多就没怎么研究(其实也是很简单的:P),所以就不再累赘,后面还有更令人兴奋的东西呢!


代码如下:


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


     if ( lpGUID != NULL )
     {
         if (( lpTemp = malloc( sizeof(GUID))) == NULL )
             return( TRUE );


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


     ComboBox_AddString( hCombo, lpszDesc );
     ComboBox_SetItemData( hCombo,
     ComboBox_FindString( hCombo, 0, lpszDesc ), lpTemp );  //编辑者注:此句有误
     return( TRUE );
}


创造DirectSound对象


创造DirectSound对象最简单的方法是调用DirectSoundCreate函数。


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


该函数的第一个参数是硬件设备,NULL表示使用默认的设备,第二个参数是远程指针LPDIRECTSOUND的地址,也就是创造的DirectSound对象放置的地址,第三个参数必须为NULL,暂时没有用。
当没有相应的设备或设备在别的程序的控制下不能响应你的呼叫时,函数返回出错,这时,如果你的程序继续工作,所有和DirectSound对象相关的操作都将不可进行!


设置合作级


因为WINDOWS是一多任务环境,可以允许多个应用程序同时工作,当然也会产生多个程序在同时里使用同一设备工作的情况,通过合作级别,DirectX可以保证所有的程序在使用同一设备时不会发生冲突(大家和平共处岂不是一件乐事),所以每个使用DirectSound的程序都应该有一合作级别用来决定允许访问的设备。
DirectSound有四种合作级别:标准级、优先级、独占级和写主缓冲级(write-primary,写是主要的动作),其中游戏普遍使用优先级这种级别可以使程序在同一采样条件下作出最柔韧的输出(MS的文档也很有文学味的嘛!)。


//C的例子,会换成C++的吗?不会说NO吧!
HRESULT hr = lpDirectSound->lpVtbl->SetCooperativeLevel( lpDirectSound, hwnd, DSSCL_PRIORITY );


标准级(DSSCL_NORMAL):该级别只能使用22KHZ、立体声(STEREO)、8位的音乐,并且不能直接的写主缓冲,也不能使用压缩过的声音。


优先级(DSSCL_PAIORITY):可以实现硬件混合(hardware mixing),可以设置主缓冲的声音格式(可以根据需要来使用不同质量的音乐)和压缩过的音乐。


独占级(DSSCL_EXCLUSIVE):当应用程序在前台工作时,其它程序是不可使用声音的。


写主缓冲级(DSSCL_WRITEPRIMARY):最高的合作级,程序可以直接的操纵主缓冲,而且程序必须直接的写主缓冲区(最基层的操作)。在这种级别,第二缓冲将不可用。
除了该级别外,所有试图LOCK主缓冲的操作都将失败,也就是说只有该级别可以对主缓冲进行写操作!
当使用写主缓冲级的程序处于前台时,后台所有程序的第二缓冲都将停止且丢失,而如果这时使用写主缓冲级的程序转到后台工作,它的主缓冲也将丢失并且在又一次转到前台工作时应该还原(restore)。更多的信息将在缓冲区管理里阐述(现在还没有翻译:P不过用过了DDraw以后这些东西应该很熟悉了才是)。
如果要设置写主缓冲级,先应该确定现在是否可以使用该级别——使用IDirectSound::GetCaps函数,检查DSCAPS结构里是否有DSCAPS_EMULDRIVER标志。


检索硬件信息


DirectSound允许应用程序检索硬件信息,当然,在一般情况下,这样做是不必要的,因为DirectSound可以自动有效的使用硬件加速,我们完全可以不用去管硬件是否具有某些能力,不过为了提高程序的效率,这样做还是是有用处的。
检索硬件信息使用IDirectSound::GetCaps方法,例如:


DSCAPS dscaps;


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


扬声器的设置


DirectSound的扬声器设置可以用来调节输出音量的大小和优化3D效果。
在WIN98和WIN2000,可以通过IDirectSound::GetSpeakerConfig来获得当前扬声器的设置,并通过IDirectSound::SetSpeakerConfig来改变扬声器设置;而在WIN95里IDirectSound::GetSpeakerConfig只是简单的返回一默认值或是返回使用IDirectSound::SetSpeakerConfig设置后的值。


压缩


应用程序可以通过IDirectSound::Compact来获得最多的可用内存,当然从前面对合作级别的讨论我们可以发现使用压缩的前提是程序的合作级别至少应该是优先级。


DirectSound缓冲
基础(又是费话连篇)




  在初始化DirectSound时,它会自动地为你的程序创建一主缓冲,这个主缓冲的作用就是混音并送到输出设备。
除了主缓冲外,程序至少还应该创建一个辅助缓冲,辅助缓冲的作用是储存将要使用的声音,它可以在不使用的时候释放掉(费话,不过这也就是暗示我们主缓冲是不可释放的)。


你可以在同一段物理内存上创建两个或更多的辅助缓冲(使用IDirectSound::DuplicateSoundBuffer方法),但是如果最初的缓冲(原本)所在的硬件内存资源不支持多缓冲,那么这个调用将以失败告终。
DirectSound还可以同时播放多个声音,当然其大前提是硬件允许。
DirectSound播放一声音只需要很短的时间延迟,如果在播放声音的同时来播放动画,你将感觉不到延迟,但是如果DirectSound需要通过软件仿真来完成这一动作,那么延迟时间将延长5-8倍。
通常情况下,你并不需要和主缓冲打交道,DirectSound会自己来管理它的,除非你要使用自己写的混音部分,这时,DirectSound就会让你自行管理主缓冲,更详细的讲解见主缓冲的存取部分。


静态缓冲和流缓冲


在应用程序里,辅助缓冲可以有两种——静态缓冲(一段内存空间一段完整的声音;好处在于可以一次将全部的声音存入缓冲)和流缓冲(并不将全部的数据一次读入缓冲,而是在播放声音时动态的读入;其好处在于占用空间较小),它们可以分别适应不同的程序需求。
一般的说,如果声音是需要再三播放的,而且容量有限(好比游戏音效),那么使用静态缓冲就更有助于提高程序的效率,相反,如果是很冗长的音乐,还是使用流缓冲的好。


创建辅助缓冲


//创建辅助缓冲的例子
BOOL AppCreateBasicBuffer( LPDIRECTSOUND lpDirectSound, LPDIRECTSOUNDBUFFER *lplpDsb )
{
     PCMWAVEFORMAT pcmwf;
     DSBUFFERDESC dsbdesc;
     HRESULT hr;


     //设置PCMWAVEFORMAT结构
     memset(&pcmwf, 0, sizeof(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结构
     memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); //将结构置0.
     dsbdesc.dwSize = sizeof(DSBUFFERDESC);
     //使用默认的设置(音量之类)
     dsbdesc.dwFlags = DSBCAPS_CTRLDEFAULT;
     //3秒钟长度的缓冲(3-second buffer)
     dsbdesc.dwBufferBytes = 3 * pcmwf.wf.nAvgBytesPerSec;
     dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf;
     //创建缓冲
     hr = lpDirectSound->lpVtbl->CreateSoundBuffer(lpDirectSound,
     &dsbdesc, lplpDsb, NULL);
     if SUCCEEDED(hr)
     {
         //成功
         return TRUE;
     }
     else
     {
         //失败
         *lplpDsb = NULL;
         return FALSE;
     }
}


很简单是吧,只要填两个STRUCT就OK了。


因为DirectSound对先创建的缓冲优先分配硬件资源,所以你应该先创建重要的缓冲。如果你事先声明要创建一硬件缓冲(和放在显存里的表面差不多),就应该在DSBUFFERDESC结构里设置DSBCAPS_LOCHARDWARE标志,但是如果你得不到足够的硬件资源(硬件内存或混音容量hardware memory or mixing capacity),将无法创建缓冲。
创建缓冲时,也可以声明是静态缓冲(设置DSBCAPS_STATIC标志)还是流缓冲;默认值是流缓冲(上面就是使用的默认值)。
缓冲是和DirectSound对象相关联的,如果释放了DirectSound对象,则它所有的缓冲也都将被释放。


缓冲控制选项


你创建一辅助缓冲时,还应该声明该缓冲需要用到的控制选项。这项工作需要你为DSBUFFERDESC结构设置以DSBCAPS_CTRL为首的标志(这些标志可以是单独的来使用,也可以同时设置几个)。
可用的控制有3-D属性、频率、Pan(左右正道的差值)、音量、Position notification(可能是指播放时的进度)。
为了能在所有的声卡上都可以获得做好的效果,最好只设置需要的控制选项。如果一块声卡支持硬件缓冲但不支持底盘控制(pan control),那么DiractSound只会在DSBCAPS_CTRLPAN标志没有被声明时使用硬件加速。这也就是说,DirectSound通过控制选项来决定如何为缓冲来分配硬件资源。
如果你使用一个缓冲不支持的控制,譬如为一个并没有声明DSBCAPS_CTRLVOLUME标志的缓冲调用IDirectSoundBuffer::SetVolume方法,是不可能成功的。


主缓冲的存取


如果你不满意DirectSound的工作,可以直接的操纵主声音缓冲,也可以说是直接的操纵硬件了,但是这将意味着DirectSound的部分特性不可用,包括辅助缓冲的混音和混音的硬件加速。
主缓冲其实是硬件缓冲,它的大小是由硬件来决定的,而这个值通常是很小的,因此你应该使用数据流的方式来访问该缓冲。而且如果硬件不提供主缓冲,你就不能直接的访问它了(其实是访问DX软件仿真的主缓冲);你应该调用IDirectSoundBuffer::GetCaps方法来检查DSBCAPS结构里是否有DSBCAPS_LOCHARDWARE标志,有才可以设置DSSCL_WRITEPRIMARY合作级别来访问主缓冲。


//写主缓冲时的初始化工作
BOOL AppCreateWritePrimaryBuffer( LPDIRECTSOUND lpDirectSound, LPDIRECTSOUNDBUFFER *lplpDsb, LPDWORD lpdwBufferSize, HWND hwnd )
{
     DSBUFFERDESC dsbdesc;
     DSBCAPS dsbcaps;
     HRESULT hr;
     WAVEFORMATEX wf;


     //初始化WAVEFORMATEX 结构
     memset(&wf, 0, sizeof(WAVEFORMATEX));
     wf.wFormatTag = WAVE_FORMAT_PCM;
     wf.nChannels = 2;
     wf.nSamplesPerSec = 22050;
     wf.nBlockAlign = 4;
     wf.nAvgBytesPerSec =
     wf.nSamplesPerSec * wf.nBlockAlign;
     wf.wBitsPerSample = 16;


     //初始化DSBUFFERDESC结构
     memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
     dsbdesc.dwSize = sizeof(DSBUFFERDESC);
     dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
     //缓冲的大小是由硬件决定的
     dsbdesc.dwBufferBytes = 0;
     dsbdesc.lpwfxFormat = NULL; //该字段必须置NULL


     //设置合作级别
     hr = lpDirectSound->lpVtbl->SetCooperativeLevel(lpDirectSound,
     hwnd, DSSCL_WRITEPRIMARY);
     if SUCCEEDED(hr)
     {
         //创建缓冲
         hr = lpDirectSound->lpVtbl->CreateSoundBuffer(lpDirectSound, &dsbdesc, lplpDsb, NULL);
         if SUCCEEDED(hr)
         {
             //设置主缓冲需要的格式
             hr = (*lplpDsb)->lpVtbl->SetFormat(*lplpDsb, &wf);
             if SUCCEEDED(hr)
             {
                 //获得主缓冲的大小
                 dsbcaps.dwSize = sizeof(DSBCAPS);
                 (*lplpDsb)->lpVtbl->GetCaps(*lplpDsb, &dsbcaps);
                 *lpdwBufferSize = dsbcaps.dwBufferBytes;
                 return TRUE;
             }
         }
     }


     //如果失败
     *lplpDsb = NULL;
     *lpdwBufferSize = 0;
     return FALSE;
}


播放声音


播放声音要通过以下步骤:
1、锁定辅助缓冲的一部分以获得你所需要的那部分缓冲的基址。
2、向缓冲写数据。
3、解锁。
4、使用IDirectSoundBuffer::Play方法来播放声音。
如果是使用的流缓冲,还需要反复的执行1-3步骤。


因为流缓冲存储通常是循环的(就像循环队列),所以当你锁定缓冲时DirectSound会返回2个指针。譬如你从一个只有4,000字节的缓冲中点开始锁定3,000字节长的数据,那么DirectSound返回的第一个指针是从中点开始的那2,000字节,而第二个指针则是缓冲最前面的那1,000字节。当然如果没有发生这种情况第二个指针是NULL。
如果你设置了DSBPLAY_LOOPING标志,那么音乐将不停的播放下去,除非你使用IDirectSoundBuffer::Stop来停止它。
有关流缓冲的部分在后继章节里还将详细的讨论到。


下面就是一个C语言的例子:
//写辅助缓冲
BOOL AppWriteDataToBuffer(
LPDIRECTSOUNDBUFFER lpDsb, //缓冲
DWORD dwOffset,             //要写入数据的缓冲徧移地址
LPBYTE lpbSoundData,        //要写入的数据
DWORD dwSoundBytes)         //一次写入的块的大小
{
     LPVOID lpvPtr1;
     DWORD dwBytes1;
     LPVOID lpvPtr2;
     DWORD dwBytes2;
     HRESULT hr;


     //获得将要写的块的地址
     hr = lpDsb->lpVtbl->Lock(lpDsb, dwOffset, dwSoundBytes, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);


     //如果返回DSERR_BUFFERLOST,还原并重新锁定
     if (DSERR_BUFFERLOST == hr)
     {
         lpDsb->lpVtbl->Restore(lpDsb);
         hr = lpDsb->lpVtbl->Lock(lpDsb, dwOffset, dwSoundBytes, &lpvPtr1, &dwAudio1, &lpvPtr2, &dwAudio2, 0);
     }


     if SUCCEEDED(hr)
     {
         //拷贝数据
         CopyMemory(lpvPtr1, lpbSoundData, dwBytes1);
         if (NULL != lpvPtr2)
         {
             CopyMemory(lpvPtr2, lpbSoundData+dwBytes1, dwBytes2);
         }
         //解锁
         hr = lpDsb->lpVtbl->Unlock(lpDsb, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
         if SUCCEEDED(hr)
         {
             //成功
             return TRUE;
         }
     }


     //失败
     return FALSE;
}


重放(PLAYBACK)的控制


你可以通过IDirectSoundBuffer::GetVolume和IDirectSoundBuffer::SetVolume来获得或设置该缓冲的音量,设置主缓冲的音量将改变声卡的设置。
同样的,你也可以通过IDirectSoundBuffer::GetFrequency和IDirectSoundBuffer::SetFrequency来获得或设置声音的频率,通过IDirectSoundBuffer::GetPan和IDirectSoundBuffer::SetPan来检索或改变左右声道的相对差,但是你不可以改变主缓冲的相应设置。


诚如前面所说的,这些缓冲控制都必须在设置了相应的标志才可以使用。


播放进度和可以被写的位置(Current Play and Write Positions)


DirectSound通常都保证缓冲里有两个指针,一个是当前的播放位置——即当前的播放进度,一个是当前的可以写数据的位置。这两个指针都只是相对缓冲而言的偏移而已。


IDirectSoundBuffer::Play方法通常都从当前的播放进度开始播放音乐。在缓冲刚建立时,播放进度是指向0,而当一段音乐播放完毕以后,播放进度指向那段音乐数据最末端的下一字节,同样的,当音乐被停止时,播放进度也指向停止位置的下一字节。


我们可以将缓冲想象成一个时钟的钟面,而这两个指针则可以作为是钟面上的两个指针。如果数据是顺时针的写上去的,那么可以被写数据的位置始终在当前的播放进度的前面——如果当前的播放进度是1,那么从2开始才是可以写数据的位置;而当播放进度到2这个位置时,从3开始才是现在可以写数据的位置了。


需要注意的是如果你使用的是流缓冲,那么你应该自己来维护现在可以写数据的位置,而且这个指针和IDirectSoundBuffer::Lock里的那个参数dwWriteCursor不是一回事,那个参数只是你想从什么位置开始写你的数据(记住是你想而不是你只能)。当然你也可以在dwFlags参数里加上DSBLOCK_FROMWRITECURSOR标志来使函数忽略dwWriteCursor参数而从当前可以写数据的那个位置开始写数据。


你可以通过IDirectSoundBuffer::GetCurrentPosition和IDirectSoundBuffer::SetCurrentPosition来检索或设置这两个指针,不过当前可以写数据的位置是不可以由你自己决定的,而应该在创建缓冲时加入DSBCAPS_GETCURRENTPOSITION2标志来保证当前可以写数据的位置是正确的。


播放缓冲时的通知(PLAY BUFFER NOTIFITACION)


在你使用流缓冲时,很可能需要知道播放进度已经到什么位置了,或者重放被停止没有。你可以通过IDirectSoundNotify::SetNotificationPositions方法来在缓冲里设置若干个通知点,当相应的事件在这些点发生时DirectSound会给予通知。但是如果音乐已经在播放了,是不允许做这些事的。


首先你应该获得IDirectSoundNotify接口的指针,就像下面一样:


// LPDIRECTSOUNDBUFFER lpDsbSecondary;
// 缓冲已经被初始化


LPDIRECTSOUNDNOTIFY lpDsNotify; //接口指针


HRESULT hr = lpDsbSecondary->QueryInterface(IID_IDirectSoundNotify, (LPVOID *)&lpDsNotify);
if SUCCEEDED(hr)
{
     //成功后就可以使用lpDsNotify->SetNotificationPositions了。
}


注意:IDirectSoundNotify接口和创建它的辅助缓冲是相关联的。


现在你可以通过WIN32 API的CreateEvent()来创建一事件对象。然后你需要为DSBPOSITIONNOTIFY结构的hEventNotify设置一句柄(CreateEvent()返回的),并且设置你想设置的通知位置的偏移值给dwOffset,就可以来设置通知位置了。


设置通知位置的例子如下:


DSBPOSITIONNOTIFY PositionNotify;


PositionNotify.Offset = DSBPN_OFFSETSTOP;
PositionNotify.hEventNotify = hMyEvent;
// hMyEvent是一个由CreateEvent()返回的句柄


lpDsNotify->SetNotificationPositions(1, &PositionNotify);


如果你需要设置更多的通知位置,你可以通过结构数组来实现。


混音(MIXING SOUND)


对DirectSound来说混音是很容易的,它允许你同时播放多个辅助缓冲,它可以自己来完成这些任务。


只要你的程序正确的指定DSBCAPS_STATIC标志,DirectSound就可以最大限度的使用硬件加速,这些标志需要在静态缓冲重新使用时再指定一次。


如果你所有的缓冲都使用同一种声音格式而且硬件输出也是使用这种格式,那么DirectSound的混音将不需要在格式转换上花任何的工夫,从而大到最优的效果(什么都是最优!:P)。


我们可以通过创建一主缓冲或是调用IDirectSoundBuffer::SetFormat方法来改变硬件输出格式,记住这主缓冲仅仅是为控制目的,和写主缓冲是不一样的,而且这种调用必须要DSSCL_PRIORITY(优先级)或更高的级别。


自己的混音


只有在DSSCL_WRITEPRIMARY级别才可以使用自己写的混音部分。在设置了合作级别后,创建主缓冲,然后锁定它,并写数据,再就可以像其它的缓冲一样的来播放了,不过需要设置DSBPLAY_LOOPING标志才可以。


下面就是一个例子:


BOOL AppMixIntoPrimaryBuffer( LPAPPSTREAMINFO lpAppStreamInfo, LPDIRECTSOUNDBUFFER lpDsbPrimary, DWORD dwDataBytes, DWORD dwOldPos, LPDWORD lpdwNewPos)
{
     LPVOID lpvPtr1;
     DWORD dwBytes1;
     LPVOID lpvPtr2;
     DWORD dwBytes2;
     HRESULT hr;


     //锁定缓冲
     hr = lpDsbPrimary->lpVtbl->Lock(lpDsbPrimary, dwOldPos, dwDataBytes, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);


     //如果返回DSERR_BUFFERLOST,还原DS并从新锁定
     if (DSERR_BUFFERLOST == hr)
     {
         lpDsbPrimary->lpVtbl->Restore(lpDsbPrimary);
         hr = lpDsbPrimary->lpVtbl->Lock(lpDsbPrimary, dwOldPos, dwDataBytes, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);
     }


     if SUCCEEDED(hr)
     {
         //将混音的数据送到缓冲区内
         CustomMixer(lpAppStreamInfo, lpvPtr1, dwBytes1);
         //该函数负责混合若干数据流。下同
         *lpdwNewPos = dwOldPos + dwBytes1;
         if (NULL != lpvPtr2)
         {
             CustomMixer(lpAppStreamInfo, lpvPtr2, dwBytes2);
             *lpdwNewPos = dwBytes2;
         }


         //解锁
         hr = lpDsbPrimary->lpVtbl->Unlock(lpDsbPrimary, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);


         if SUCCEEDED(hr)
         {
             return TRUE;
         }
     }


     //锁定或解锁失败
     return FALSE;
}

你可能感兴趣的:(DirectSound详细介绍)