缓冲区
DSound缓冲区对象控制wave数据从一个资源传输到目的地。资源可以是合成器,其它缓冲区,WAV文件或者一个资源。对于大多数的缓冲区,目的地是一个叫做主缓冲区的混音引擎。数据从主缓冲区到达硬件然后被转换成音波。
想要了解capture缓冲区,可以查看Capturing Waveforms
一个次缓冲区可以在整个程序运行期都存在或者在不需要的时候被销毁。它可以是一个包含单独短小声音的静态缓冲区,也可以是一个在播放时有新的数据会刷新的流数据缓冲区。为了限制对内存的需求,长声音通过流数据缓冲区播放,流数据缓冲区中的数据播放不会超过几秒。
次缓冲区的创建并不都是一样的,每个缓冲区都有自己的特性:
1)格式。缓冲区的格式必须和播放的声音格式匹配
2)控制。不同的缓冲区可以有不同的控制,比如声音,频率以及在2D或3D空间移动。当建立一个缓冲区,应该只指定自己需要的控制,例如,不要在没有3D环境的程序中建立3D缓冲区。
3)位置。一个缓冲区可以在硬件管理的内存中,也可以在软件管理的内存中。硬件缓冲区更有效率但是在数字上有限制。硬件缓冲区在64位操作系统中不被支持。
建立次缓冲区
要建立一个缓冲区,调用IDirectSound8::CreateSoundBuffer方法。这个方法返回一个IDirectSoundBuffer8接口。
下面的例子建立了一个次缓冲区然后返回了一个IDirectSoundBuffer8的接口
HRESULT CreateBasicBuffer(LPDIRECTSOUND8 lpDirectSound, LPDIRECTSOUNDBUFFER8* ppDsb8)
{
WAVEFORMATEX wfx;
DSBUFFERDESC dsbdesc;
LPDIRECTSOUNDBUFFER pDsb = NULL;
HRESULT hr;
// Set up WAV format structure.
memset(&wfx, 0, sizeof(WAVEFORMATEX)); WAVEFORMATEX
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 2;
wfx.nSamplesPerSec = 22050;
wfx.nBlockAlign = 4;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
wfx.wBitsPerSample = 16;
// Set up DSBUFFERDESC structure.
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags =
DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY
| DSBCAPS_GLOBALFOCUS;
dsbdesc.dwBufferBytes = 3 * wfx.nAvgBytesPerSec;
dsbdesc.lpwfxFormat = &wfx;
// Create buffer.
hr = lpDirectSound->CreateSoundBuffer(&dsbdesc, &pDsb, NULL);
if (SUCCEEDED(hr))
{
hr = pDsb->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*) ppDsb8);
pDsb->Release();
}
return hr;
}
这个例子建立了一个流缓冲区,可以保持3秒的流媒体。非流缓冲区必须足够大,以便能够容纳下整个声音。
DSBCAPS璤GLOBALFOCUS标志位保证当程序窗口不在前面时仍能够播放,没有这个标志位,当其它程序或者对话框拥有焦点的时候缓冲区会静音。
如果缓冲区的位置没有被指定,如果可能的话DirectSound会把它放到硬件控制的内存中。因为硬件的缓冲区是通过声卡处理器混音的,它们在程序执行期具有最小的冲突。
如果希望指定缓冲区的位置,在DSBUFFERDESC中设置DSBCAPS_LOCHARDWARE或者DSBCAPS_LOCSOFTWARE标志位。如果设置了DSBUFFERDESC标志位但是没有足够的硬件资源,缓冲区的建立会失败。
如果要使用DirectSound的声音管理组件,指定DSBCAPS璤LOCDEFER标志位。这个标志位推迟资源的分配,直到它被播放。为了获得更多信息,参照动态声音管理
可以使用IDirectSoundBuffer8::GetCaps方法探测到一个存在的缓冲区,并且查看DSBCAPS标志位是DSBCAPS_LOCHARDWARE还是DSBCAPS_LOCSOFTWARE。
声音缓冲对象属于创建它们的设备对象。当设备对象被释放,所有的设备对象建立的缓冲区将会被释放,并且不能被引用。
复制缓冲区
可以创建两个或者更多的缓冲区包含相同的数据。IDirectSound8::DuplicateSoundBuffer。不能复制主声音缓冲区,因为复制的缓冲区和源缓冲区共用内存。改变一个缓冲区的数据就会涉及到复制缓冲区。
缓冲区控制选项
当建立一个声音缓冲。必须指定缓冲区需要的控制选项。这个是由DSBUFFERDESC结构的标志位来做的,这些可以包含一个或多个下面表格的组合
Flag | Description |
DSBCAPS_CTRL3D | 声音可以在3D环境中移动 |
DSBCAPS_CTRLFX | 可以添加音效 |
DSBCAPS_CTRLFREQUENCY | 可以改变声音的频率 |
DSBCAPS_CTRLPAN | The sound source can be moved from left toright. |
DSBCAPS_CTRLPOSITIONNOTIFY | Notification positions can be set on thebuffer. |
DSBCAPS_CTRLVOLUME | 音量可以改变 |
特别注意一些指定的标志位绑定是不允许的,获得更多的信息,请查看DSBUFFERDESC结构
为了获得更高的执行效率,程序最好只指定那些需要用到的控制位。
DirectSound使用控制设置来决定是否分配硬件资源给声音缓冲。例如,一个设备可能支持硬件缓冲区但是在这些缓冲区中不提供pan控制。这种情况下,DirectSound应该使用硬件加速只有当DSBCAPS_STRLPAN标志位没有被指定。
如果程序尝试使用一个缓冲没有指定的控制,调用会失败。例如,如果尝使用SetVolume方法,这个方法只有当DSBCAPS_CTRLVOLUME标志位被指定的时候才会成功。否则这个方法会失败并且返回DSERR_CONTROLUNAVAIL错误码。
缓冲区的运算法则
可以建立一个包含DSBCAPS_CTRL3D控制标志的次缓冲区,可以指定一个运算法则使声音空间化如果声音在软件中。默认的,没有转移函数被执行,听众相对于声音的位置只由面板和音量决定。可以要求两种级别的转移函数。
填充和播放静态缓冲区
一个包含整个独立声音的次缓冲区叫做静态缓冲区。尽管可以重复使用同样的缓冲区播放不同的声音,资料只向静态缓冲区中写一次
静态缓冲区类似流缓冲区一样被建立,唯一的不同是静态缓冲区被填充一次然后播放,但是流缓冲区内的数据在播放时是频繁改变的。
注意:一个静态缓冲区不一定要使用DSBCAPS_STATIC标志。这个标志需要声卡的内存分配,这在大多数现在的硬件上是不被支持的。一个静态缓冲区可以存在于系统内存中,可以使用DSBCAPS_LOCHARDWARE或者DSBCAPS_LOCSOFTWARE 标志位
再如数据到静态缓冲区中需要3个步骤:
使用IDirectSoundBuffer8::Lock锁住整个缓冲区。指定缓冲区的偏移值决定从哪里开始写入,然后获得内存地址的指针。
通过获得的地址使用标准的内存拷贝函数写入声音数据
使用IDirectSoundBuffer8::Unlock.解锁
在下面的例子中展示了这些步骤。lpdsbStatic是一个IDirectSoundBuffer8接口指针,pbData是源数据地址。
LPVOID lpvWrite;
DWORD dwLength;
if (DS_OK == lpdsbStatic->Lock(
0, // Offset at which to start lock.
0, // Size of lock; ignored because of flag.
&lpvWrite, // Gets address of first part of lock.
&dwLength, // Gets size of first part of lock.
NULL, // Address of wraparound not needed.
NULL, // Size of wraparound not needed.
DSBLOCK_ENTIREBUFFER)) // Flag.
{
memcpy(lpvWrite, pbData, dwLength);
lpdsbStatic->Unlock(
lpvWrite, // Address of lock start.
dwLength, // Size of lock.
NULL, // No wraparound portion.
0); // No wraparound size.
}
else
(
ErrorHandler(); // Add error-handling here.
}
想要播放声音, 调用IDirectSoundBuffer8::Play,例如下面的例子:
lpdsbStatic->SetCurrentPosition(0);
HRESULT hr = lpdsbStatic->Play(
0, // Unused.
0, // Priority for voice management.
0); // Flags.
if (FAILED(hr))
(
ErrorHandler(); // Add error-handling here.
}
因为在例子中DSBPLAY_LOOPING标志位没被设置,播到结尾的时候缓冲区自动停止。也可以在播放过程中调用IDirectSoundBuffer8::Stop停止,当中途停下时,播放指针仍然在原来的位置。因此例子中调用IDirectSoundBuffer8::SetCurrentPosition,可以保证缓冲区从开头播放。
使用流缓冲区
流缓冲区不能一次填充到缓冲区,在播放的时候旧的数据定期地被新的数据更换。
要播放一个流缓冲区,调用IDirectSoundBuffer8::Play方法,设置DSBPLAY_LOOPING标志位。
想要停止播放,调用IDirectSoundBuffer8::Stop方法,这个方法立刻停止缓冲区,所以需要保证所有的数据都已经被播放。这个可以通过定期检测播放指针,或者设置一个通知的位置。
加入流声音需要以下的步骤:
1)探测缓冲区是否已经准备好接收新的数据。这个可以通过定期检测播放指针或者等待一个通知。
2) 使用IDirectSoundBuffer8::Lock方法锁住缓冲区的一小部分。这个方法返回一个或两个现在可以被写入的地址。
3) 使用标准内存拷贝函数写入声音数据。
4) 使用IDirectSoundBuffer8::Unlock函数解锁。
因为IDirectSoundBuffer8::Lock可能返回两个地址,所以有可能执行两次单独的内存拷贝。
尽管可以锁住整个缓冲区,但是当正在播放时我们不能这样做。通常每次只刷新缓冲区的一小部分。例如,当播放指针到达第二个四分之一时我们可以锁住前面的四分之一并且写入。不能在播放指针和写入指针之间写入数据。
下面的程序向一个声音缓冲中写入数据,在dwOffset偏移处开始:
BOOL AppWriteDataToBuffer(
LPDIRECTSOUNDBUFFER8 lpDsb, // The buffer.
DWORD dwOffset, // Our own write cursor.
LPBYTE lpbSoundData, // Start of our data.
DWORD dwSoundBytes) // Size of block to copy.
{
LPVOID lpvPtr1;
DWORD dwBytes1;
LPVOID lpvPtr2;
DWORD dwBytes2;
HRESULT hr;
// Obtain memory address of write block. This will be in two parts
// if the block wraps around.
hr = lpDsb->Lock(dwOffset, dwSoundBytes, &lpvPtr1,
&dwBytes1, &lpvPtr2, &dwBytes2, 0);
// If the buffer was lost, restore and retry lock.
if (DSERR_BUFFERLOST == hr)
{
lpDsb->Restore();
hr = lpDsb->Lock(dwOffset, dwSoundBytes,
&lpvPtr1, &dwBytes1,
&lpvPtr2, &dwBytes2, 0);
}
if (SUCCEEDED(hr))
{
// Write to pointers.
CopyMemory(lpvPtr1, lpbSoundData, dwBytes1);
if (NULL != lpvPtr2)
{
CopyMemory(lpvPtr2, lpbSoundData+dwBytes1, dwBytes2);
}
// Release the data back to DirectSound.
hr = lpDsb->Unlock(lpvPtr1, dwBytes1, lpvPtr2,
dwBytes2);
if (SUCCEEDED(hr))
{
// Success.
return TRUE;
}
}
// Lock, Unlock, or Restore failed.
return FALSE;
}