交互DircetX DirectSound 以及 Direct3Dsound音频媒体的程序设计.docx
《交互DircetX DirectSound 以及 Direct3Dsound音频媒体的程序设计.docx》由会员分享,可在线阅读,更多相关《交互DircetX DirectSound 以及 Direct3Dsound音频媒体的程序设计.docx(15页珍藏版)》请在冰豆网上搜索。
![交互DircetX DirectSound 以及 Direct3Dsound音频媒体的程序设计.docx](https://file1.bdocx.com/fileroot1/2023-6/23/f0f0a632-e3d0-470b-a75e-7af6b60bc846/f0f0a632-e3d0-470b-a75e-7af6b60bc8461.gif)
交互DircetXDirectSound以及Direct3Dsound音频媒体的程序设计
微软所提供开发工具包括一系列专为游戏和交互DircetXDirectSound以及Direct3Dsound音频媒体的程序设计人员开发的强有力的工具。
DirectX充分利用了声音加速硬件以尽可能的提高其运行速度,减少CPU的占用时间。
但是,音频信号仍然对整个系统的运行产生着重大影响。
本文所描述的技术将帮助用户使用DirectSound以及Direct3DSound以减少音频重放对系统运行的影响。
前言
微软所提供的开发工具中DirectX波形声音重放设备是为支持在Windows95和WindowsNT中开发游戏和交互媒体应用程序而设计的。
DirectSound和Direct3DSound允许你在同一个三维空间中同时运行多个声音文件和移动声音源。
只要有可能DirectX,将充分利用声音加速设备来改善运行状况和减少CPU的使用。
但这并不是说你可在三维程序空间运行、编译充斥着大量声音代码的程序,并且随心所欲地将其移来移去。
如果你并没有注意如何使用计算机的声音资源你将很快发现,你的计算机的CPU周期绝大部分被你自己添加的一个户外历险游戏的44.1khz,16位立体声的优美声音所占去。
技巧和技术
首先,我们来谈谈一些有关的定义。
你所熟悉的DirectSound包括以下一些术语:
从缓冲区:
是指用来执行波形数据的应用程序缓冲区。
每个执行的波形文件都有一个从缓冲区,每个这样的缓冲区都有自己特定的格式。
主缓冲区:
是DirectSound的输出缓冲区。
一般说来,应用程序并非将波形数据直接写入主缓冲区。
相反,DirectSound首先在从缓冲区中将波形数据加以合成,然后输入主缓冲区中。
注意:
仅有一个主缓冲区,并且其格式决定了其输出格式。
静态缓冲区:
包含了内存中的一个完整的声音。
因为,通过一次简单操作,你能在缓冲区中写入一个完整的声音。
所以,他们对于用户十分方便。
静态缓冲区通过声卡的合成加速执行。
流缓冲区:
仅仅为声音的一部分,利用它,我们并不需要大量的内存就能运行较长的声音文件。
在利用流缓冲区时,用户必须周期性的在声音缓冲区中写入数据。
但是,流缓冲区不能在硬件中进行声音合成。
我们将再次提到DirectSound合成器。
这种DirectSound元件负责从缓冲区中将声音中按位隔行合成。
然后执行诸如:
音量大小、均衡调节(左右声道平衡)、频率转换,以及三维操纵等操作。
当合成器不能识别你通过API存放的组件时(不同于上面所述的任一控制操作),这正是DirectSound的CPU核心所要做的。
一些运行问题将在“DirectSound合成器发生了什么情况”的项目中加以讨论。
下面的关于合成器和主、从缓冲区之间关系的示意图,将使读者清楚地了解他们之间的关系。
图通过一个简单的示意图阐明缓冲区和DirectSound合成器之间的关系
如果DirectSound开发小组成员见到上面的示意图,他们将对此不屑一顾。
因为,合成器远比上图所展示的优秀。
上图中,我并没有任何与合成器相关的三维组件,以及其他类型的进程。
既然,我们获得的背景知识远比上面所述的多,我们将进一步接触其他一系列对我们有用的东西。
以下是帮助你最大限度的使用DirectSound功能的一个技术列表。
巧妙使用声音的技巧;
在主缓冲区中使用相同的格式;
将主缓冲区设置为最低存储速率的格式;
在短促频繁的无声时间间隔内连续地使用主缓冲区;
尽可能地使用硬件进行声音合成;
最大限度的保证控制变换;
使用延时的三维进程命令;
下面我们将对上面所述的各项技术作详细的说明。
一.巧妙使用声音的技巧
DirectSound的最为优秀的特征之一就是独立演奏、控制多声道音频信号的能力。
一旦声音的设计者真正地掌握了它们,那么,真可以称得上是一本万利。
唯一的花费只是CPU指令周期。
每个你所使用的从缓冲区都将消耗CPU指令周期。
每一次诸如频率量化等的进程操作都将带来CPU指令周期的额外消耗。
三维声音将比常规声音消耗更多的CPU指令周期。
这些读者能够想象得到吗?
你应该同你的声音设计者坐下来一起探讨一下全方位地利用声音演奏带来的强烈震撼(如果你自己本身就是程序员及声音设计者,那么你就自己一个人静下心来仔细领略一下),思考一下究竟是哪种声音在将你的渴望以久的经历传递给用户的过程中起着最为重要的作用。
当可能减少从缓冲区的使用时,请采用声音预混技术。
例如:
如果你正在一个声道中模仿蟋蟀的低吟,而在另一个声道中记录了田蛙的欢唱以烘托出夏夜的氛围时,请将它们合成到一个声道中。
如果在你的脑海中有应用程序的初步方案,并且在以后将对其进行折衷处理的时候,你应该对该进程进行相当程度的简化。
但要记住,想要设计出相当精练的实用声音程序,你需要长时间地字斟句酌。
Beatles的Sgt.Pepper'sLonelyHeartsClubBand就是一个有创意的伟大杰作。
它被记录在一盘四声道盒式录音带中。
与此形成对照,现代的音频记录设备提供了至少四十八个声道,并能提供真正的可用来进行实用合成的无限多声道的盒式录音带以及可利用的MIDI序列发生器。
二.在主缓冲区中使用相同的数据格式
DirectSound合成器将每个从缓冲区中的数据转换为主缓冲区的数据格式,这种数据的转换是在数据进入主缓冲区的合成过程中实现的,它也将占用CPU周期。
你可以在保证从缓冲区(例如波形文件)和主缓冲区具有相同的数据格式的前提下消除这笔开销。
事实上,正是由于DirecSound的这种格式转换方式,你所要做的仅仅是对比样例速率和声道数,即使样例速率(8位或16位)存在一些差别也没有关系,因为它唯一的后果只是降低主缓冲区的数据存取速率。
到目前为止,大多数声卡都是ISA总线卡,它通过DMA方式将声音数据从系统内存移动到当地缓冲区中,处理器在进行内存读写之前将被迫等待DMA的数据传输结束,这势必会影响到CPU的运行速度。
对于ISA总线声卡,上述的数据传送方式无疑将对系统的运行产生不可回避的影响,但并不会对新型的32位PCI卡产生任何影响。
对于DirectSound,DMA数据传输的影响直接关系到数据的输出速率以及主缓冲区的访问速率。
我曾听说过这样的趣事:
在一台主频为90Mhz的奔腾机上运行基本格式为44.1Khz,16位的立体声音乐程序,DMA将占用多达30%的CPU指令周期!
DMA数据传输正是影响DirectSound运行的最大因素。
值得庆幸的是,上述问题在你无法顺利执行时非常容易处理。
实验表明,减少数据存取速率的最好办法是改变主缓冲区中的数据格式。
这里的转换十分明显,运行改变了声音的品质,要改变主缓冲区中的数据格式,只需调用方法IDirectSoundBuffer:
:
Setformat,但不要忘记:
你的协作层设置成DSSCL.PRIOR99vY或DSSCL_EXCLUSIVE,以避免主缓冲区的耗费。
三.在无声时间间隔中连续使用主缓冲区
DMA同时从另一个方面影响着系统的运行。
当没有声音播放时,DirectSound停止了合成器的工作和DMA的活动。
如果你的程序中存在着短促频繁的无声时间间隔,在每次声音播放时使合成随声音播放时的间断而起起停停,将比你让合成器一直处于连续的工作状态的情况更糟。
在这种情况下,你可以在主缓冲区中强制性的调用方法PLAY使合成器处于激活状态。
这样,即使在没有声音播放时,合成器也将连续工作。
此时,为了恢复停止合成器的缺省方式,我们可以在主缓冲区中调用方法STOP。
四.使用硬件进行声音合成
如果系统中装配了支持声卡的DirectSound驱动器,大多数声卡都支持一定水平的硬件合成。
下面的一段小窍门将允许你尽量使用硬件合成。
在你进行硬件声音合成时使用静态缓冲区。
DirectSound将试图在静态缓冲中进行声音合成。
为你用得最多的声音文件建立声音缓冲区(可用来进行声音硬件合成的合成器是有一定的限度的。
在声音文件运行时,使用方法IDirectSound:
:
GetCaps决定声音加速硬件支持何种格式,并尽可能的采用这些格式(一些声卡只能合成特定格式的声音文件。
例如:
SoundBlasterAWE32声卡只能合成单16位格式的声音文件)。
当你调用CreatSoundBuffer建立从缓冲区时,你得建立静态缓冲区,在结构DSBUFFERDESC的dwPlag区域设定DSBCAPS_LOCHARDWARE标志。
你也可以通过设定DSBCAPS_LOCHARDWARE标志将缓冲区的数据进行强制性的硬件合成。
但是,硬件合成所要使用的资源不可用时,CreatSoundBuffer就将出错。
方法IDirectSound:
:
GetCaps为我们提供了关于声音加速能力的详尽描述,这对于我们进行存取操作有很大的邦助。
我们在其工作时间内可以调用GetCaps,调整音频系统以最佳方式使用硬件资源。
在DirectX文档中查看结构DSAPS和标志DSCAPS.dwFlags可使我们准确了解到一些系统的有用信息。
五.最低限度的进行声音控制变换
在从合成器中改变均衡、音量或频率也将影响到应用程序的运行。
为了防止声音输出时的中断的产生,DirectSound合成器必须提前20到100毫秒,甚至更多的时间进行声音合成。
当你进行声音控制变换时,合成器不得不刷新正在进行的声音合成的缓冲区的信息,重新合成以适应适应的变化。
比较好的方法是尽量减少送入系统的控制改变次数。
这在按流或群输入时显得尤为重要。
同时,我们应尽量减少日常的调用SetVolume、SetPn、SetFrequency的不连续操作。
例如:
如果你进行帧同步的定期检测,需要将适应从左声道扬声器移动到右声道扬声器时,你就应该每帧调用SetPan一次,而不是每帧两次。
注意:
三维控制变换(方向、位置、速度、多普勒因子等等)也将引起DirectSound合成器在其先前进行合成的缓冲区中重新合成。
但是,你也可以将一系列三维控制变换集合在一组中,这将只使DirectSound合成器只进行一次重新合成。
请仔细阅读以下关于延时控制变换详细说明的章节。
六.使用延时三维进程命令
正如我在前面所说的那样,三维声音将比常规声音花费更多的CPU指令周期。
这是因为在每一个合成周期内,为计算出三维立体声音效将占用更多的CPU指令周期。
你应该尽可能地减少使用三维立体声,最好不要使用那些并不对你真正有用的三维声音。
这是通过实践得出的在你运行程序时影响整个系统工作的另一因素。
在设计你的应用程序时,你应该尽早进行尝试,使其更加容易使声音具有或没有三维效果。
你也可以调用具有DS3DMODE_DISABLE标志的方法IDirectSound:
:
SetMode,使得三维进程能够在三维缓冲区中运行。
改变三维声音缓冲区以及听众进行的诸如对方位、速度、多普勒因子的操作,都将引起DirectSound合成器重新合成先前合成缓冲区中的信息,当然,这将浪费一定的CPU指令周期。
为了使三维设置的改变对系统运行的影响降低到最小程度,你就应该使用延时三维进程命令。
这是DirectSound三维声音组件所特有的特征为了使用三维延时进程命令,请设定每个三维设置变换(SetPosition,SetVelocity等等)中的方法IDirect3DListener或Direct3DSoundBuffer的dwApply参数的DS3DMODE_DISABLE标志,并且将所有的这些变化制成一帧,随后,再调用IDirect3DListener:
:
CommitDeferredSettings去执行所有的延时命令,在先前的合成缓冲区中进行一次重新合成。
结论
我已经为读者罗列了一系列利用DirectX优化音频媒体的特殊工具。
我所能给予你的最好的建议是:
设计你的支持运行、监视和音量调节的音频子系统。
毫无疑问,你必须保证你有充足的时间等待你的程序的运行!
如果你从一开始就将程序运行的协调性考虑进去,这个任务将变得更加容易。
#include
#include
#include
#include "resource.h"
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dsound.lib")
#pragma warning(disable :
4996)
#define Safe_Release(p) if((p)) (p)->Release();
// .WAV file header
struct WAVE_HEADER
{
char riff_sig[4]; // 'RIFF'
long waveform_chunk_size; // 8
char wave_sig[4]; // 'WAVE'
char format_sig[4]; // 'fmt ' (notice space after)
long format_chunk_size; // 16;
short format_tag; // WAVE_FORMAT_PCM
short channels; // # of channels
long sample_rate; // sampling rate
long bytes_per_sec; // bytes per second
short block_align; // sample block alignment
short bits_per_sample; // bits per second
char data_sig[4]; // 'data'
long data_size; // size of waveform data
};
// window handles, class and caption text.
HWND g_hwnd;
char g_class_name[] = "WavPlayClass";
IDirectSound8* g_ds; // directsound component
IDirectSoundBuffer8* g_ds_buffer; // sound buffer object
//--------------------------------------------------------------------------------
// Create wave header information from wave file.
//--------------------------------------------------------------------------------
IDirectSoundBuffer8* Create_Buffer_From_WAV(FILE* fp, WAVE_HEADER* wave_header)
{
IDirectSoundBuffer* ds_buffer_main;
IDirectSoundBuffer8* ds_buffer_second;
DSBUFFERDESC ds_buffer_desc;
WAVEFORMATEX wave_format;
// read in the header from beginning of file
fseek(fp, 0, SEEK_SET);
fread(wave_header, 1, sizeof(WAVE_HEADER), fp);
// check the sig fields. returning if an error.
if(memcmp(wave_header->riff_sig, "RIFF", 4) || memcmp(wave_header->wave_sig, "WAVE", 4) ||
memcmp(wave_header->format_sig, "fmt ", 4) || memcmp(wave_header->data_sig, "data", 4))
{
return NULL;
}
// setup the playback format
ZeroMemory(&wave_format, sizeof(WAVEFORMATEX));
wave_format.wFormatTag = WAVE_FORMAT_PCM;
wave_format.nChannels = wave_header->channels;
wave_format.nSamplesPerSec = wave_header->sample_rate;
wave_format.wBitsPerSample = wave_header->bits_per_sample;
wave_format.nBlockAlign = wave_format.wBitsPerSample / 8 * wave_format.nChannels;
wave_format.nAvgBytesPerSec = wave_format.nSamplesPerSec * wave_format.nBlockAlign;
// create the sound buffer using the header data
ZeroMemory(&ds_buffer_desc, sizeof(DSBUFFERDESC));
ds_buffer_desc.dwSize = sizeof(DSBUFFERDESC);
ds_buffer_desc.dwFlags = DSBCAPS_CTRLVOLUME;
ds_buffer_desc.dwBufferBytes = wave_header->data_size;
ds_buffer_desc.lpwfxFormat = &wave_format;
// create main sound buffer
if(FAILED(g_ds->CreateSoundBuffer(&ds_buffer_desc, &ds_buffer_main, NULL)))
return NULL;
// get newer interface
if(FAILED(ds_buffer_main->QueryInterface(IID_IDirectSoundBuffer8, (void**)&ds_buffer_second)))
{
ds_buffer_main->Release();
return NULL;
}
// return the interface
return ds_buffer_second;
}
//--------------------------------------------------------------------------------
// Load sound data from second directsound buffer.
//--------------------------------------------------------------------------------
BOOL Load_Sound_Data(IDirectSoundBuffer8* ds_buffer, long lock_pos, long lock_size, FILE* fp)
{
BYTE* ptr1;
BYTE* ptr2;
DWORD size1, size2;
if(lock_size == 0)
return FALSE;
// lock the sound buffer at position specified
if(FAILED(ds_buffer->Lock(lock_pos, lock_size, (void**)&ptr1, &size1, (void**)&ptr2, &size2, 0)))
return FALSE;
// read in the data
fread(ptr1, 1, size1, fp);
if(ptr2 !
= NULL)
fread(ptr2, 1, size2, fp);
// unlock it
ds_buffer->Unlock(ptr1, size1, ptr2, size2);
return TRUE;
}
//--------------------------------------------------------------------------------
// Load wave file.
//--------------------------------------------------------------------------------
IDirectSoundBuffer8* Load_WAV(char* filename)
{
IDirectSoundBuffer8* ds_buffer;
WAVE_HEADER wave_header = {0};
FILE* fp;
// open the source file
if((fp = fopen(filename, "rb")) == NULL)
return NULL;
// create the sound buffer
if((ds_buffer = Create_Buffer_From_WAV(fp, &wave_header)) == NULL)
{
fclose(fp);
return NULL;
}
// read in the data
fseek(fp, sizeof(WAVE_HEADER), SEEK_SET);
// load sound dat