|
楼主 |
发表于 2006-10-8 17:37:25
|
显示全部楼层
很简单是吧,只要填两个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:lay方法来播放声音。 如果是使用的流缓冲,还需要反复的执行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:lay方法通常都从当前的播放进度开始播放音乐。在缓冲刚建立时,播放进度是指向0,而当一段音乐播放完毕以后,播放进度指向那段音乐数据最末端的下一字节,同样的,当音乐被停止时,播放进度也指向停止位置的下一字节。
我们可以将缓冲想象成一个时钟的钟面,而这两个指针则可以作为是钟面上的两个指针。如果数据是顺时针的写上去的,那么可以被写数据的位置始终在当前的播放进度的前面——如果当前的播放进度是1,那么从2开始才是可以写数据的位置;而当播放进度到2这个位置时,从3开始才是现在可以写数据的位置了。
需要注意的是如果你使用的是流缓冲,那么你应该自己来维护现在可以写数据的位置,而且这个指针和IDirectSoundBuffer:ock里的那个参数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, &ositionNotify);
如果你需要设置更多的通知位置,你可以通过结构数组来实现。
混音(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; }
|
|