DirectSound应用程序开发快速入门
摘要:DirectSound编程的入门介绍,通过实例讲解了如何利用DirectSound最基本的功能:播放音频,并提供了DirectSound播放音频文件的两种方式:静态缓存和流缓存。
关键词:DirectSound、流缓存、静态缓存、wave文件播放
一、配置DirectSound的开发环境
在进行DirectSound开发之前,一定要设置好开发环境,否则编译时会提示很多东西都找不到定义,DirectSound的开发环境很好设置,简单的说就是包含一些头文件,将lib文件添加要工程中。仅仅包含dsound.h肯定是不够的,一般来说,在工程中包含下面两个文件就够了。
#include <mmsystem.h>
#include <dsound.h>
如果还想使用Dsound的API的话,那么你就要在vc开发环境中添加Dsound..lib库,如果程序还提示有很多的外部链接找不到,那么建议将下面的库都添加到你的工程中comctl32.lib dxerr9.lib winmm.lib dsound.lib dxguid.lib odbc32.libodbccp32.lib。
开发环境配置好了,就可以在工程中任意使用DirectSound提供的接口和函数了。下面简单介绍DirectSound开发中要用到的对象。
二、 DirectSound几个对象
首先了解一下DirectSound中常用的几个对象, DirectSound其实很简单,主要有下面常用的几个对象。
对象 |
数量 |
作用 |
主要接口 |
设备对象 |
每个应用程序只有一个设备对象 |
用来管理设备,创建辅助缓冲区 |
IDirectSound8 |
辅助缓冲 区对象 |
每一个声音对应一个辅助缓冲区,可以有多个辅助缓冲区 |
用来管理一个静态的或者动态的声音流,然后在主缓冲区中混音 |
IDirectSoundBuffer8, |
主缓冲 区对象 |
一个应用程序只有一个主缓冲区 |
将辅助缓冲区的数据进行混音,并且控制3D参数. |
IDirectSoundBuffer, IDirectSound3DListener8 |
特技对象 |
没有 |
来辅助缓冲的声音数据进行处理 |
8个特技接口IDirectSoundFXChorus8 |
首先,要创建一个设备对象,然后通过设备对象创建缓冲区对象。辅助缓冲区由应用程序创建和管理,DirectSound会自动地创建和管理主缓冲区,一般来说,应用程序即使没有获取这个主缓冲区对象的接口也可以播放音频数据,但是,如果应用程序要想得到IDirectSound3DListener8接口,就必须要自己创建一个主缓冲区。
三、播放音频文件开发的基本流程
下面简单的学习一下如何通过DirectSound的API播放声音,这里只是简单的介绍一下播放声音的步骤。
第一步:创建一个设备对象,设置设备对象的协作度。
在代码中你可以通过调用DirectSoundCreate8函数来创建一个支持IDirectSound8接口的对象,这个对象通常代表缺省的播放设备。当然你可以枚举可用的设备,然后将设备的GUID传递给DirectSoundCreate8函数。
如果没有声音输出设备,这个函数就返回error,或者,在VXD驱动程序下,如果声音输出设备正被某个应用程序通过waveform格式的API函数所控制,该函数也返回error。
下面是创建对象的代码:
LPDIRECTSOUND8 lpDirectSound;
HRESULT hr = DirectSoundCreate8(NULL, & lpDirectSound, NULL));
注意:DirectSound虽然基于COM,但是你并不需要初始化COM库,这些DirectSound都帮你做好了,当然,如果使用DMOS特技,就要自己初始化COM库了。
因为Windows是一个多任务操作环境,在同一个时刻有可能多个应用程序共用同一个设备,通过协作水平,DirectX就可以保证这些应用程序在访问设备的时候不会冲突,每个DirectSound应用程序都有一个协作度,用来确定来接近设备的程度,当你创建完设备对象后,一定要调用IDirectSound8::SetCooperativeLevel来设置协作度,否则,会听不到声音的。
HRESULThr = lpDirectSound->SetCooperativeLevel(hwnd, DSSCL_PRIORITY);
if(FAILED(hr))
ErrorHandler(hr); // Adderror-handling here.
第二步:创建一个辅助Buffer,也叫后备缓冲区
可以通过IDirectSound8::CreateSoundBuffer来创建buffer对象,这个对象主要用来获取处理数据,这种buffer称作辅助缓冲区,以和主缓冲区区别开来,DirectSound通过把几个后备缓冲区的声音混合到主缓冲区中,然后输出到声音输出设备上,达到混音的效果。
第三步:获取PCM类型的数据
将WAV文件或者其他资源的数据读取到缓冲区中。
第四步,将数据读取到缓冲区
可以通过 IDirectSoundBuffer8::Lock.方法来准备一个辅助缓冲区来进行写操作,通常这个方法返回一个内存地址,数据从私人buffer中复制到这个地址中,然后调用IDirectSoundBuffer8::Unlock方法。
第五步:播放缓冲区中的数据
可以通过IDirectSoundBuffer8::Play方法来播放缓冲区中的音频数据,可以通过IDirectSoundBuffer8::Stop来暂停播放数据,可以反复的停止,播放音频数据,如果同时创建了几个buffer,那么就可以同时来播放这些数据,这些声音会自动进行混音的。
可以通过IDirectSoundBuffer8::GetVolume和SetVolume函数来获取或者设置正在播放的音频的音量的大小。
如果设置主缓冲区的音量就会改变声卡的音频的声量大小。音量的大小,用分贝表示,一般没法来增强缺省的音量,这里要提示一下,分贝的增减不是线形的,减少3分贝相当于减少1/2的能量。最大值衰减100分贝几乎听不到了。
通过IDirectSoundBuffer8::GetFrequency和SetFrequency方法可以获取设置音频播放的频率,主缓冲区的频率不允许改动,通过 IDirectSoundBuffer8::GetPan 和SetPan函数可以设置音频在左右声道播放的位置,具有3D特性的缓冲区没法调整声道。
四、使用静态的缓冲区
如果wave文件不是很大,那么可以使用静态缓冲区。
包含全部音频数据的缓冲区我们称为静态的缓冲区,尽管,不同的声音可能会反复使用同一个内存buffer,但严格来说,静态缓冲区的数据只写入一次。
静态缓冲区的创建和管理和流缓冲区很相似,唯一的区别就是它们使用的方式不一样,静态缓冲区只填充一次数据,然后就可以play,然而,流缓冲区是一边play,一边填充数据。
给静态缓冲区加载数据分下面几个步骤
1、调用IDirectSoundBuffer8::Lock函数来锁定所有的内存,你要指定你锁定内存中你开始写入数据的偏移位置,并且取回该偏移位置的地址。
2、采用标准的数据copy方法,将音频数据复制到返回的地址。
3、调用IDirectSoundBuffer8::Unlock.,解锁该地址。
下面给出使用static buffer 播放wav文件的完整代码,首先定义需要的一些对象:
LPDIRECTSOUNDBUFFER8 g_pDSBuffer8 = NULL; //buffer
LPDIRECTSOUND8 g_pDsd = NULL; //dsound
CWaveFile*g_pWaveFile= NULL;
//下面初始化DirectSound工作。
HRESULThr;
if(FAILED(hr= DirectSoundCreate8(NULL,&g_pDsd,NULL)))
return FALSE;
//设置设备的协作度
if(FAILED(hr= g_pDsd->SetCooperativeLevel(m_hWnd,DSSCL_PRIORITY)))
return FALSE;
g_pWaveFile= new CWaveFile;
g_pWaveFile->Open(_T("d:\\test.wav"),NULL,WAVEFILE_READ);
DSBUFFERDESCdsbd;
ZeroMemory(&dsbd, sizeof(DSBUFFERDESC) );
dsbd.dwSize= sizeof(DSBUFFERDESC);
dsbd.dwFlags= DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLFX| DSBCAPS_CTRLPOSITIONNOTIFY|DSBCAPS_GETCURRENTPOSITION2;
dsbd.dwBufferBytes= g_pWaveFile->GetSize();//MAX_AUDIO_BUF * BUFFERNOTIFYSIZE ;
dsbd.lpwfxFormat= g_pWaveFile->m_pwfx;
LPDIRECTSOUNDBUFFERlpbuffer;
//创建辅助缓冲区对象
if(FAILED(hr = g_pDsd->CreateSoundBuffer(&dsbd,&lpbuffer,NULL)))
return ;
if(FAILED( hr = lpbuffer->QueryInterface( IID_IDirectSoundBuffer8, (LPVOID*)&g_pDSBuffer8) ) )
return ;
lpbuffer->Release();
//准备工作做完了,下面就开始播放了
LPVOIDlplockbuf;
DWORDlen;
DWORDdwWrite;
g_pDSBuffer8->Lock(0,0,&lplockbuf,&len,NULL,NULL,DSBLOCK_ENTIREBUFFER);
g_pWaveFile->Read((BYTE*)lplockbuf,len,&dwWrite);
g_pDSBuffer8->Unlock(lplockbuf,len,NULL,0);
g_pDSBuffer8->SetCurrentPosition(0);
g_pDSBuffer8->Play(0,0,DSBPLAY_LOOPING);
五、使用流缓冲区播放超大型的wave文件
流缓冲区用来播放那些比较长的音频文件,因为数据比较长,没法一次填充到缓冲区中,一边播放,一边将新的数据填充到DirectSound的缓冲区中。
可以通过IDirectSoundBuffer8::Play函数来播放缓冲区中的内容,注意在该函数的参数中一定要设置DSBPLAY_LOOPING标志。
通过IDirectSoundBuffer8::Stop方法中断播放,该方法会立即停止缓冲区播放,因此要确保所有的数据都被播放,你可以通过拖动播放位置或者设置通知位置来实现。
将音频流倒入缓冲区需要下面三个步骤
1、确保缓冲区已经做好接收新数据的准备。可以拖放播放的光标位置或者等待通知。
2、调用IDirectSoundBuffer8::Lock.函数锁住缓冲区的位置,这个函数返回一个或者两个可以写入数据的地址
3、使用标准的copy数据的方法将音频数据写入缓冲区中
4、IDirectSoundBuffer8::Unlock.,解锁
这里要说明一下DirectSound的通知机制。因为流缓存大小只够容纳一部分数据,因此,在播放完缓冲区中的数据后,DirectSound就会通知应用程序,将新的数据填充到DirectSound的缓冲区中。假如设置DirectSound的buffersize 为1920*4,如下图:
可以给DirectSound设置一个事件,并且设置buffer通知大小,如下:
HANDLEg_event[MAX_AUDIO_BUF];
for(inti =0; i< MAX_AUDIO_BUF;i++)
{
g_aPosNotify[i].dwOffset =i* BUFFERNOTIFYSIZE ;
g_aPosNotify[i].hEventNotify= g_event[i];
}
if(FAILED(hr= g_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID *)&g_pDSNotify )))
return ;
g_pDSNotify->SetNotificationPositions(MAX_AUDIO_BUF,g_aPosNotify);
g_pDSNotify->Release();
当DirectSound播放到buffer的1920,3840,5760,7680等位置时,DirectSound就会通知应用程序,将g_ event,设置为通知态,应用程序就可以通过WaitForMultipleObjects 函数等待DirectSound的通知,将数据填充到DirectSoun的辅助缓冲区。
下面我给出流缓存播放wave文件的代码。
#defineMAX_AUDIO_BUF 4
#defineBUFFERNOTIFYSIZE 1920
LPDIRECTSOUNDBUFFER8 g_pDSBuffer8 = NULL; //buffer
LPDIRECTSOUND8 g_pDsd = NULL; //dsound
CWaveFile*g_pWaveFile= NULL;
BOOLg_bPlaying = FALSE; //是否正在播放
LPDIRECTSOUNDNOTIFY8 g_pDSNotify = NULL;
DSBPOSITIONNOTIFYg_aPosNotify[MAX_AUDIO_BUF];//设置通知标志的数组
HANDLEg_event[MAX_AUDIO_BUF];
DWORDg_dwNextWriteOffset = 0;
//初始化DirectSound
HRESULThr;
if(FAILED(hr= DirectSoundCreate8(NULL,&g_pDsd,NULL)))
returnFALSE;
if(FAILED(hr= g_pDsd->SetCooperativeLevel(m_hWnd,DSSCL_PRIORITY)))
returnFALSE;
g_pWaveFile= new CWaveFile;
g_pWaveFile->Open(_T("d:\\test.wav"),NULL,WAVEFILE_READ);DSBUFFERDESC dsbd;
ZeroMemory(&dsbd, sizeof(DSBUFFERDESC) );
dsbd.dwSize= sizeof(DSBUFFERDESC);
dsbd.dwFlags= DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY|DSBCAPS_GETCURRENTPOSITION2;
dsbd.dwBufferBytes= MAX_AUDIO_BUF * BUFFERNOTIFYSIZE ;
dsbd.lpwfxFormat= g_pWaveFile->m_pwfx;
LPDIRECTSOUNDBUFFERlpbuffer;
//创建DirectSound辅助缓冲区
if(FAILED(hr= g_pDsd->CreateSoundBuffer(&dsbd,&lpbuffer,NULL)))
return FALSE;
if(FAILED( hr = lpbuffer->QueryInterface( IID_IDirectSoundBuffer8, (LPVOID*)&g_pDSBuffer8) ) )
return FALSE;
lpbuffer->Release();
//设置DirectSound通知 机制
for(inti =0; i< MAX_AUDIO_BUF;i++)
{
g_aPosNotify[i].dwOffset =i* BUFFERNOTIFYSIZE ;
g_aPosNotify[i].hEventNotify= g_event[i];
}
if(FAILED(hr=g_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID*)g_pDSNotify )))
return ;
g_pDSNotify->SetNotificationPositions(MAX_AUDIO_BUF,g_aPosNotify);
g_pDSNotify->Release();
在下面的play函数中,我们就要单独启动一个线程,来播放了
voidOnBnClickedButtonPlay()
{
g_bPlaying =TRUE;
g_pWaveFile->ResetFile();
CreateThread(0,0,PlayThread,this,NULL,NULL);
}
//停止播放音频
voidCDsoundEffectDemoDlg::OnBnClickedButtonStop()
{
g_bPlaying=FALSE;
Sleep(500);
g_pDSBuffer8->Stop();
}
下面看看播放线程,在线程里,首先将音频数据填充到DirectSound的辅助缓冲区中,然后调用DirectSound buffer 的play方法,开始播放,然后就在WaitForMultipleObjects 等待DirectSound的通知,然后读取wave文件将数据填充到DirectSound的空buffer中。
DWORDWINAPI PlayThread(LPVOID lpParame)
{
DWORDres;
LPVOIDlplockbuf;
DWORDlen;
DWORDdwWrite;
g_pDSBuffer8->Lock(0,0,&lplockbuf,&len,NULL,NULL,DSBLOCK_ENTIREBUFFER);
g_pWaveFile->Read((BYTE*)lplockbuf,len,&dwWrite);
g_pDSBuffer8->Unlock(lplockbuf,len,NULL,0);
g_pDSBuffer8->SetCurrentPosition(0);
g_pDSBuffer8->Play(0,0,DSBPLAY_LOOPING);
g_dwNextWriteOffset= 0;
while(g_bPlaying)
{
res = WaitForMultipleObjects (MAX_AUDIO_BUF, g_event, FALSE, INFINITE);
if(res > WAIT_OBJECT_0)
ProcessBuffer();
}
return0;
}
下面的函数主要是给空的DirectSound缓冲区填充 音频数据。
voidProcessBuffer()
{
DWORDdwBytesWrittenToBuffer = 0;
VOID*pDSLockedBuffer = NULL;
VOID*pDSLockedBuffer2 = NULL;
DWORDdwDSLockedBufferSize;
DWORDdwDSLockedBufferSize2;
HRESULThr;
g_pDSBuffer8->Lock(g_dwNextWriteOffset,BUFFERNOTIFYSIZE,&pDSLockedBuffer,&dwDSLockedBufferSize,&pDSLockedBuffer2,&dwDSLockedBufferSize2,0);
if(hr == DSERR_BUFFERLOST)
{
g_pDSBuffer8->Restore();
g_pDSBuffer8->Lock(g_dwNextWriteOffset,BUFFERNOTIFYSIZE,&pDSLockedBuffer,&dwDSLockedBufferSize,&pDSLockedBuffer2,&dwDSLockedBufferSize2,0);
}
if(SUCCEEDED(hr))
{
g_pWaveFile->Read((BYTE*)pDSLockedBuffer,dwDSLockedBufferSize,&dwBytesWrittenToBuffer);
g_dwNextWriteOffset +=dwBytesWrittenToBuffer;
if (NULL !=pDSLockedBuffer2)
{
g_pWaveFile->Read((BYTE*)pDSLockedBuffer2,dwDSLockedBufferSize2,&dwBytesWrittenToBuffer);
g_dwNextWriteOffset+= dwBytesWrittenToBuffer;
}
g_dwNextWriteOffset %= (BUFFERNOTIFYSIZE * MAX_AUDIO_BUF);
if(dwBytesWrittenToBuffer <BUFFERNOTIFYSIZE )
{
FillMemory( (BYTE*) pDSLockedBuffer + dwBytesWrittenToBuffer,
BUFFERNOTIFYSIZE - dwBytesWrittenToBuffer,
(BYTE)(g_pWaveFile->m_pwfx->wBitsPerSample== 8 ? 128 : 0 ) );
g_bPlaying = FALSE;
}
hr =g_pDSBuffer8->Unlock(pDSLockedBuffer,dwDSLockedBufferSize,
pDSLockedBuffer2,dwDSLockedBufferSize2);
}
}
六、结束语
本文介绍DirectSound开发的入门知识,开发环境的配置,并通过实例代码介绍了DirectSound播放音频文件的两种方法。