4DirectSound开发高级技巧
4.1Dsound驱动模型(DirectSound Driver Models)
在VXD驱动模型下,所有的DirectSound的混音工作都是由Dsound.vxd来完成的,一个虚拟的设备驱动程序。Dsound.vxd也提供操作声卡从Cpu接收数据的缓冲区的方法,这其实和DirectSound的主缓冲区是类似的。DirectSound应用程序可以给主缓冲区设置特定的属性,例如,采样频率,或者采样精度,也就改变了硬件设备。
在WDM驱动模式下,DirectSound并不直接操作硬件,当然,操作硬件加速缓冲区除外。相应的,DirectSound将数据送往内核混音器,内核混音器的工作就是将多个格式的音频流调整为一个统一的格式,将它们进行混音,然后将结果送到硬件上进行播放。其实,它的工作和Dsound.vxd的工作类似。一个重要的区别在于Dsound.vxd仅仅对DirectSound的内存缓冲区进行混音,内核混音器会对所有的windows音频数据进行混音,包括那些使用waveOut win32函数输出数据的应用,DirectSound和Wave格式的音频输出设备不能同时打开在WDM驱动模式下是不成立的。
最重要的是内核混音器和音频硬件的关系,内核混音器就是一个系统中的软件,可以用来指定硬件的DMA缓冲区。它根据它要进行混音的音频数据格式,来选择硬件的格式,它会将它混音的音频数据以一种高质量的形式进行输出,或者根据硬件的情况选择近似的音频质量。
这里有一个很重要的暗示,就是DirectSound不能设置硬件的DMA缓冲区格式。对于你的应用程序而言,硬件格式其实就是根据你实际播放的音频的格式来定的。如果你play的是44kHZ,内核混音器就会将所有的数据都混音成44kHZ,同时也保证硬件以44Khz进行输出。
作为应用程序的开发者,你没法来选择系统驱动模式,因为驱动模式的选择有声卡类型,windows版本,以及安装的驱动程序来控制。由于这个原因,你在测试你的程序时要注意所有的情况,DirectSound或许用的是Dsound.vxd或者用的是内核混音器,你要确保你的应用程序对两种方式都支持。
4.2设置硬件的扩展属性(System Property Sets)
4.3Property Sets for DirectSound Buffers
DirectSound设备的属性可以通过下面的类对象来获取,该类的ID为CLSID_DirectSoundPrivate (11AB3EC0-25EC-11d1-A4D8-00C04FC28ACA). 该类支持IKsPropertySet接口,CLSID和属性的定义在Dsconf.h文件中可以找到。
1要想创建该类的对象,首先要通过LoadLibrary加载Dsound.dll
2调用GetProcAddress函数来获取DllGetClassObject函数接口。
3通过DllGetClassObject函数来获取IClassFactory接口,CLSID_DirectSoudnPrivate类厂对象接口。
4调用IClassFactory::CreateInstance来创建CLSID_DirectSoundPrivate对象,并获取IKsPropertySet接口(IID_IKsPropertySet)。
CLSID_DirectSoundPrivate对象只支持一个属性设置接口DSPROPSETID_DirectSoundDevice(84624f82-25ec-11d1-a4d8-00c04fc28aca)这个属性设置接口暴露了下面三个只读的属性
DSPROPERTY_DIRECTSOUNDDEVICE_WAVEDEVICEMAPPING
DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION
DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE
DSPROPERTY_DIRECTSOUNDDEVICE_WAVEDEVICEMAPPING这个属性根据指定的设备的名称来获取该设备的GUID。
获取的属性数据包含在
DSPROPERTY_DIRECTSOUNDDEVICE_WAVEDEVICEMAPPING_DATA结构里,这个结构包含下面的成员
Member |
Type |
Description |
DeviceName |
String |
[in] Name of device |
DataFlow |
DIRECTSOUNDDEVICE_DATAFLOW |
[in] Direction of data flow, either DIRECTSOUNDDEVICE_DATAFLOW_RENDER or DIRECTSOUNDDEVICE_DATAFLOW_CAPTURE |
DeviceID |
GUID |
[out] DirectSound device ID |
DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION属性
该属性可以返回指定GUID设备的全部的属性描述,可以通过下面的结构返回数据
DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA这个结构的成员如下
Member |
Type |
Description |
Type |
DIRECTSOUNDDEVICE_TYPE |
[out] Device type: DIRECTSOUNDDEVICE_TYPE_EMULATED, DIRECTSOUNDDEVICE_TYPE_VXD or DIRECTSOUNDDEVICE_TYPE_WDM |
DataFlow |
DIRECTSOUNDDEVICE_DATAFLOW |
[in, out] Direction of data flow, either DIRECTSOUNDDEVICE_DATAFLOW_RENDER or DIRECTSOUNDDEVICE_DATAFLOW_CAPTURE |
DeviceID |
GUID |
[in] DirectSound device GUID, or NULL for the default device of the type specified by DataFlow |
Description |
String |
[out] Description of DirectSound device |
Module |
String |
[out] Module name of the DirectSound driver |
Interface |
String |
[out] PnP device interface name |
WaveDeviceID |
ULONG |
[out] Identifier of the corresponding Windows Multimedia device |
DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE属性
这个属性用来枚举所有的DirectSound的播放或者录音设备。可以通过
DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_DATA结构将属性值返回,这个结构的成员如下
Member |
Type |
Description |
Callback |
LPFNDIRECTSOUNDDEVICEENUMERATECALLBACK |
[in] Application-defined callback function. WhenIKsPropertySet::Get is called, this function is called once for each device enumerated. It takes as parameters aDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATAstructure describing the enumerated device, and the value in Context. |
Context |
LPVOID |
[in] User-defined value to be passed to the callback function for each device enumerated. |
4.4如何优化Directsound(Optimizing DirectSound Performance)
主要讲三个方面的内容,如何用硬件进行混音,动态声音管理,有效地使用缓冲区
许多声卡都拥有自己的辅助缓冲区,可以处理3-D特技和混音特技。这些缓冲区,就像它们的名字一样,其实是存在于系统的内存中,而不是在声卡上,但是这些缓冲区是由声卡来直接操作的,所以比需要处理器来控制软件缓冲区,速度要快许多。所以,在硬件条件允许的条件下要多申请硬件的缓冲区,特别是3-D缓冲区。
缺省的情况下,DirectSound会优先申请硬件缓冲区,但是能够申请多少硬件的缓冲区是由硬件设备的性能决定的。硬件在同一时刻能播放的声音数量越多,可申请的硬件缓冲区的数量就越多,当创建一个缓冲区时,DirectSound就分配一个hardware voice,缓冲区销毁时就释放hardware voice。如果一个应用程序创建了很多的缓冲区,但是,很多缓冲区是由软件来销毁的,也就是说,这些缓冲区是由CPU而不是声卡来控制和混音的。
注意:DirectSound 声音管理器分配的是硬件混音资源,并不是缓冲区,在一个PCI板卡上,缓冲区在分配前后都占用相同大小的内存,不管是将该缓冲区分配给硬件还是软件混音器。
动态的声音管理,通过在缓冲区播放才进行voice allocation,获取提前结束那么权限比较低的音频数据播放,释放他们的资源,从而可以减轻硬件设备的压力。
当缓冲区正在play的时候,为了延迟硬件资源用来分配给混音,3-D特技,可以在创建缓冲区对象时,将DSBUFFERDESC结构的dwFlages设置为DSBCAPS_LOCDEFER标志,传递给IDirectSound8::CreateSoundBuffer.函数,当你对这样的缓冲区进行IDirectSoundBuffer8::Play orIDirectSoundBuffer8::AcquireResources操作时,DrectSound会尽可能的在硬件上play。
当调用Play的时候,你可以试图通过传递下面表格中的参数将其他正在使用的硬件 voice资源释放掉。从而供你的buffer使用
DSBPLAY_TERMINATEBY_TIME |
Select the buffer that has been playing longer than any other candidate buffers. |
DSBPLAY_TERMINATEBY_DISTANCE |
Select the 3-D candidate buffer farthest from the listener. |
DSBPLAY_TERMINATEBY_PRIORITY |
Select the buffer that has the lowest priority of candidate buffers, as set in the call to Play. If this is combined with one of the other two flags, the other flag is used only to resolve ties. |
DirectSound会根据你设置的标志,对所有正在play的buffer进行搜索,如果找到符合条件的缓冲区,DirectSound就会停掉该资源,然后将该资源分配给你的心的缓冲区使用。如果没有找到符合条件的缓冲区,那么你的新创建的缓冲区只好在软件缓冲区播放了,当然,如果你设置了DSBPLAY_LOCHARDWARE标志,此时,play调用就会失败。
下面我们看看如何更有效地使用缓冲区。
当使用流缓冲区的时候,要限制通知的次数和数据读写的次数。不要创建有很多通知positions的缓冲区,或者太小的缓冲区。流缓冲区在小于3个通知位置时工作的效率最高。当你改变一个辅助缓冲区的控制项时,此时效率就会受影响,所以,要尽可能少的调用IDirectSoundBuffer8::SetVolume,IDirectSoundBuffer8::SetFrequency.
IDirectSoundBuffer8::SetPan, 函数,例如,你有个习惯总是喜欢在控制面板上来回的拖动左右声道的位置。
一定要记住,3D缓冲区是需要占用的更多的CPU的。所以,要注意下面的事情
1将经常播放的sounds放到硬件缓冲区中,
2 Don't create 3-D buffers for sounds that won't benefit from the effect.
3通过IDirectSound3DBuffer8::SetMode函数,设置DS3DMODE_DISABLE标志来停止对3d缓冲区的3d处理,
4最好批量的参数进行调整。
4.5向主缓冲区写数据(Writing to the Primary Buffer)
当应用程序需要一些特殊的混音或者特技,而辅助缓冲区不支持这些功能,那么DirectSOund允许直接曹操主缓冲区,
当你获得操作主缓冲区的权限时,其他的DirectSound特性就变得不可用了,辅助缓冲区没法混音,硬件加速混音器也无法工作。
大多数的应用程序应该使用辅助缓冲区避免直接操作主缓冲区,因为可以申请大块的辅助缓冲区,可以提高足够长的写入时间,从而避免了音频数据产生缝隙的危险。
只有当主缓冲区硬件时你才能操作它,所以你可以通过IDirectSoundBuffer8::GetCaps函数来查询,该函数的参数结构dwFlages成员设置为DSBCAPS_LOCHARDWARE,如果你想锁定一个正在被软件枚举的主缓冲区,会失败的。
你通过IDirectSound8::CreateSoundBuffer函数来创建主缓冲区,只要设置DSBCAPS_PRIMARYBUFFER标志即可。同时要保证你的协作度为DSSCL_WRITEPRIMARY。
下面的代码演示了如何获取向主缓冲区写数据的权限
BOOL AppCreateWritePrimaryBuffer(
LPDIRECTSOUND8 lpDirectSound,
LPDIRECTSOUNDBUFFER *lplpDsb,
LPDWORD lpdwBufferSize,
HWND hwnd)
{
DSBUFFERDESC dsbdesc;
DSBCAPS dsbcaps;
HRESULT hr;
WAVEFORMATEX wf;
// Set up wave format structure.
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;
// Set up DSBUFFERDESC structure.
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
// Buffer size is determined by sound hardware.
dsbdesc.dwBufferBytes = 0;
dsbdesc.lpwfxFormat = NULL; // Must be NULL for primary buffers.
// Obtain write-primary cooperative level.
hr = lpDirectSound->SetCooperativeLevel(hwnd, DSSCL_WRITEPRIMARY);
if SUCCEEDED(hr)
{
// Try to create buffer.
hr = lpDirectSound->CreateSoundBuffer(&dsbdesc,
lplpDsb, NULL);
if SUCCEEDED(hr)
{
// Set primary buffer to desired format.
hr = (*lplpDsb)->SetFormat(&wf);
if SUCCEEDED(hr)
{
// If you want to know the buffer size, call GetCaps.
dsbcaps.dwSize = sizeof(DSBCAPS);
(*lplpDsb)->GetCaps(&dsbcaps);
*lpdwBufferSize = dsbcaps.dwBufferBytes;
return TRUE;
}
}
}
// Failure.
*lplpDsb = NULL;
*lpdwBufferSize = 0;
return FALSE;
}
下面的代码演示了应用程序如何混音的,其中CustomMixer函数是用来将几个音频流混音的函数。AppMixIntoPrimaryBuffer 应该定时的被调用。
BOOL AppMixIntoPrimaryBuffer(
APPSTREAMINFO* lpAppStreamInfo,
LPDIRECTSOUNDBUFFER lpDsbPrimary,
DWORD dwDataBytes,
DWORD dwOldPos,
LPDWORD lpdwNewPos)
{
LPVOID lpvPtr1;
DWORD dwBytes1;
LPVOID lpvPtr2;
DWORD dwBytes2;
HRESULT hr;
// Obtain write pointer.
hr = lpDsbPrimary->Lock(dwOldPos, dwDataBytes,
&lpvPtr1, &dwBytes1,
&lpvPtr2, &dwBytes2, 0);
// If DSERR_BUFFERLOST is returned, restore and retry lock.
if (DSERR_BUFFERLOST == hr)
{
lpDsbPrimary->Restore();
hr = lpDsbPrimary->Lock(dwOldPos, dwDataBytes,
&lpvPtr1, &dwBytes1,
&lpvPtr2, &dwBytes2, 0);
}
if SUCCEEDED(hr)
{
// Mix data into the returned pointers.
CustomMixer(lpAppStreamInfo, lpvPtr1, dwBytes1);
*lpdwNewPos = dwOldPos + dwBytes1;
if (NULL != lpvPtr2)
{
CustomMixer(lpAppStreamInfo, lpvPtr2, dwBytes2);
*lpdwNewPos = dwBytes2; // Because it wrapped around.
}
// Release the data back to DirectSound.
hr = lpDsbPrimary->Unlock(lpvPtr1, dwBytes1,
lpvPtr2, dwBytes2);
if SUCCEEDED(hr)
{
return TRUE;
}
}
// Lock or Unlock failed.
return FALSE;
}