DirectSound 技术详解

首先分析directsound的接口对象的API创建函数,DirectSoundCreate8(LPCGUID lpcGuidDevice,LPDIRECTSOUND8 * ppDS8,LPUNKNOWN pUnkOuter);
函数DirectSoundCreate8后面的数字其实是该directx版本的最新支持的创建函数(这里我用的是directx 9.0 的SDK),

以后见到的数字基本都是关于版本问题.上面函数的lpcGuidDevice是声卡设备的全局标志,一般为NULL,表示默认声卡,以后见到变量类型为LPUNKNOWN的,都应肯定该参数是没用的,有待以后的开发使用,我们应该把该参数设置为NULL,第二个参数就是directsound的接口对象.

当创建好direcrtsound接口对象时,必须为该对象设置协调级别便于声卡设备与其他WINDOWS程序共享设备.因此,必须调用SetCooperativeLevel(HWND hwnd,DWORD dwLevel); hwnd就是我们当前的应该程序的窗口句柄,参数dwLevel是协调级别,有以下几种取值:1.DSSCL_EXCLUSIVE也就是当前程序独占设备,在directx 8.0以上版本它的作用可以用另外一个取值代替.2.DSSCL_PRIORITY具有优先设置DirectSound设备的权限.在此标志下,directsound必然是独占的.(可调用SetFormat设置主次缓冲区的播放格式).3.DSSCL_NORMAL正常协调级别,其他程序可共享声卡设备进行播放.此标志下,不允许修改主缓冲区的格式,而且会默认在声卡内存中申请到一个8bit和22KHz的单声道主缓冲区.4.DSSCL_WRITEPRIMARY可写主缓冲区,此时主缓冲区不可播放处理,就是说不能把数据放进混声器中,再传到主缓冲区.所有的处理只能在主缓冲区中进行.(可以完全控制声音的播放).

    主缓冲区的创建:

这里我们需要用到CreateSoundBuffer(LPCDSBUFFERDESC pcDSBufferDesc,LPDIRECTSOUNDBUFFER *ppDSBuffer,LPUNKNOWN pUnKOuter);同样的道理pUnkOuter为NULL,ppDSBuffer缓冲区指针,pcDSBufferDesc缓冲区描述结构.

DSBUFFERDESC 结构体定义如下:

struct {

DWORD dwSize;//结构体大小

DWORD dwFlags;//缓冲区的用途标志

DWORD dwBufferBytes;//缓冲区大小(如果是设置主缓冲区必须设置为0)

DWORD dwReserved;//保留未用,必须为0

LPWAVEFORMATEX lpwfxFormat;//声音的格式属性

GUID    guid3DAlgorithm;//3D声效算法,一般不必理会

};

dwflag 参数的标志说明:

DSBCAPS_CTRL3D   //缓冲区具有的3D音效控制能力,不能与DSBCAPS_CTRLPAN一起使用

DSBCAPS_CTRLFREQUENCY   //可设置采样频率

DSBCAPS_CTRLFX  //缓冲区支持特效处理,但缓冲区必须够大,可容纳更多的数据

DSBCAPS_CTRLPAN  //缓冲区可以控制声道

DSBCAPS_CTRLPOSITIONNOTIFY  //缓冲区具有播放位置通知能力

DSBCAPS_CTRLVOLUME  //缓冲区可设置音量大小

DSBCAPS_GLOBALFOCUS  //缓冲是一个全局声音资源,当前程序切换到其他程序依然可以继续播放.

DSBCAPS_LOCDEFER  //缓冲区可绑定硬件内存或者软件内存来播放声音

DSBCAPS_LOCHARDWARE  //缓冲区必须使用硬件的混声器,如果不支持硬件内存或者混声器,都会导致创建缓冲区失败.

DSBCAPS_LOCSOFTWARE  //缓冲区使用软件内存或者使用软件混音

DSBCAPS_MUTE3DATMAXDISTANCE  //超过声音可听的最大距离,将停止播放声音

DSBCAPS_PRIMARYBUFFER  //说明缓冲区的为主缓冲区(如果没说明,则用作次缓冲区)

DSBCAPS_STATIC  //自动使用硬件内存做缓冲区

DSBCAPS_STICKYFOCUS  //当程序切换到其他不使用DIRECTSOUND 的程序时,缓冲区继续播放声音,但无法如常进行其他处理

如果要设置声音的格式,就必须先了解WAVEFORMATEX这个结构体

struct WAVEFORMATEX

{

WORD wFormatTag; //指明压缩算法 如.wav文件为WAVE_FORMAT_PCM

WORD nChannels;  //声道数量(一般1~2个)

DWORD nSamplesPerSec;  //每秒采样的次数

DWORD nAvgBytesPerSec;  //每秒采样的总字节数=nSamplesPerSec*nBlockAlign

WORD nBlockAlign;  //一次采样的字节数=(nChannels*wBitsPerSamples)/8

WORD wBitsPerSample;  //8bit 或16bit,即一个模拟信号的大小所分配的bit位数

WORD cbSize;  //附加的数据大小,对于WAVE_FORMAT_PCM类型的数据,设为0

};

在创建主缓冲时,我们在DSBUFFERDESC结构体中的dwFlag标志中只须要说明为DSBCAPS_PRIMARYBUFFER

dwBufferBytes必须为0;lpwfxFormat必须为NULL.

如果要改变主缓冲区的格式,必须在创建默认格式下的主缓冲区后,调用SetFormat,然后传入刚才了解的WAVEFORMATEX的结构体.

SetFormat的原型是:SetFormat(LPCWAVEFORMATEX pcfxFormat);

    创建次缓冲区

首先,我们先了解.wav的文件储存格式,它的文件头包括3块:RIFF.fmt.data;RIFF块总共占12字节,4字节为名称"RIFF",4字节为RIFF的数据的大小4,4字节为RIFF的数据"WAVE";fmt块总共占24字节,分别有4字节名称"fmt ",4字节的fmt数据大小为16,fmt的数据又分为2字节的格式标志,2字节的声道数,4字节的采样频率,4字节的每秒采样字节数,2字节的每次采样的字节数,2字节采样的位数;data块名称占4字节,实际声音数据的大小数占4字节,剩下的就是实际的声音数据.

当我们创建了次缓冲区后,我们可以用这样的一个结构体

struct WaveHeader

{

char RiffID[4];

long WaveformChunkSize;

char WaveID[4];

char ForamtID[4];

long FormatChunkSize;

short FormatTag;

short Channels;

long SampleRate;

long BytesPerSec;

short BlockAlign;

short BitsPerSample;

char DataID[4];

long DataSize;

};

利用这样的一个结构体,我们可以从一个音频文件获取音频文件的相关信息,并且可以利用它获取音频文件的实际的声音数据.并将数据读进次缓冲区中.

 下面为利用上面的结构体获取文件信息并创建对应的次缓冲区:
FILE *fp

if((fp=fopen("sweet.wav","rb"))==NULL)

   return false;

WaveHeader * pWaveHeader=new WaveHeader();

fseek(fp,0,SEEK_SET);

fread(pWaveHeader,1,sizeof(WaveHeader),fp);

if(memcmp(pWaveHeader->RiffID,"RIFF",4)||memcmp(pWaveHeader->WaveID,"WAVE",4)||memcmp(pWaveHeader->FormatID,"fmt ",4)||memcmp(pWaveHeader->DataID,"data",4))

    return false;

WAVEFORMATEX pWaveFormat;

ZeroMemory(&pWaveFormat,sizeof(WAVEFORMATEX));

pWaveFormat.wFormatTag=pWaveHeader->FormatTag;

pWaveFormat.nChannels=pWaveHeader->Channels;

pWaveFormat.nSamplePerSec=pWaveHeader->SampleRate;

pWaveFormat.wbitsPerSample=pWaveHeader->BitsPerSample;

pWaveFormat.nBlockAlign=(pWaveFormat.wBitsPerSample/8)*pWaveFormat.nChannels;

pWaveFormat.nAvgBytesPerSec=pWaveFormat.nSamplePerSec*pWaveFormat.nBlockAlign;

DSBUFFERDESC pBufferDesc;

ZeroMemory(&pBufferDesc,sizeof(DSBUFFERDESC));

pBufferDesc.dwsize=sizeof(DSBUFFERDESC);

pBufferDesc.dwFlags=DSBCAPS_CTRLVOLUME|DSBCAPS_GLOBALFOCUS|DSBCAPS_STATIC;

pBufferDesc.dwBufferBytes=pWaveHeader->DataSize;

pBufferDesc.lpwfxFormat=&pWaveFormat;

//m_pDirectSound为已经创建的directsound的接口对象

//pDirectSoundBuffer为次缓冲区的接口指针

//pDirectSoundBuf8为最新版本次缓冲区的接口指针(可以认为原接口指针需要更新为最新版本才能使用)

if(FAILED(m_pDirectSound->CreateSoundBuffer(&pBufferDesc,&pDirectSoundBuffer,NULL)))

    return E_FAIL;

if(FAILED(pDirectSoundBuffer->QueryInterface(IID_IDirectSoundBuffer8,(void **)&pDirectSoundBuf8)))

{

    pDirectSoundBuffer->Release();

    return E_FAIL;

}

接下来就是将声音数据装入次缓冲区:

我们要将数据传入次缓冲区,那么必须用到两个函数LOCK()与UNLOCK();

首先看看LOCK函数的原型:

HRESULT LOCK(DWORD dwoffset, //锁定缓冲区的起此位置

DWORD dwBytes, //锁定的字节数

LPVOID * ppvAudioPtr1,//第一位置指针

LPDWORD pdwAudioBytes1,//第一缓冲区的大小

LPVOID * ppvAudioPtr2,//第二位置指针(本人认为这样应该是提高了数据存取的速度)

LPDWORD pdwAudioBytes2,//第二缓冲区的大小

DWORD dwFlags  //锁定标志,默认为DSBLOCK_FROMWRITECURSOR=0

);

Unlock(LPVOID pvAudioPtr1,DWORD dwAudioBytes1,LPVOID pvAudioPtr2,DWORD dwAudioBytes2);

接着,把刚才得到的sweet.wav的数据放入次缓冲中.

BYTE * pSoundPtr1=NULL;

BYTE * pSoundPtr2=NULL;

DWORD dwSoundSize1,dwSoundSize2;

if(FAILED(pDirectSoundBuf8->Lock(0,pWaveHeader->DataSize,(void **)&pSoundPtr1,&dwSoundSize1,(void **)&pSoundPtr2,&dwSoundSize2,0)))

   return E_FAIL;

fread(pSoundPtr1,1,dwSoundSize1,fp);

if(pSoundPtr2 !=NULL)

    fread(pSoundPtr2,1,dwSoundSize2,fp);

//写入完成后解锁

pDirectSoundBuf8->Unlock(pSoundPtr1,dwSoundSize1,pSoundPtr2,dwSoundSize2);

最后就是声音的播放和控制

声音的播放和控制其实很简单,播放只需要调用idrectsoundbuffer8的Play函数,其实这个函数是将次缓冲区的声音数据送到混声器中,与其他声音进行混合,最后输到主缓冲区自动播放.

Play函数原型:Play(DWORD dwReserved1,//保留

DWORD dwPriority,//优先权, 未使用DSBCAPS_LOCDEFER 必须设置为0

DWORD dwFlags//播放方式 可以使用true  or false 来控制是否使用循环播放还是播放一次 也可以使用 DSBPLAY_LOOPING,DSBPLAY_LOCHARDWARE(只能通过硬件中的缓冲区播放),DSBPLAY_LOCSOFTWARE(只能通过软件中的缓冲区进行播放).

)

如果要控制播放位置,可以调用directsoundbuffer8的SetCurrentPosition函数.

SetCurrentPosition函数原型:SetCurrentPosition(DWORD dwNewPosition //便宜位置);

如果要停止只需要调用directsoundbuffer8的stop函数,该函数没参数.

如果要设置声音音量,可以调用directsoundbuffer8的SetVolume函数.

函数原型:SetVolume(LONG lVolume//音量值);最大值DSBVOLUME_MAX=0;DSBVOLUME_MIN=-10000;

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/l76988469s/archive/2008/09/08/2901674.aspx

你可能感兴趣的:(网络游戏开发)