关键词: Directsound stream buffer ,static buffer wave 文件播放
一、配置DirectDounf的开发环境
在进行DirectSound开发之前,一定要设置好开发环境,否则编译时会提示你很多东西都找不到定义,DirectSound的开发环境很好设置,简单的说就是包含一些头文件,将lib文件添加要工程中。仅仅包含dsound.h肯定是不够的,一般来说,在你的工程中包含下面两个文件就够了。
#include <mmsystem.h> #include <dsound.h> |
开发环境配置好了。你可以在你的工程中任意使用DirectSound提供的接口和函数了。下面简单介绍DirectSound开发中要用到的对象。
二、DiectDound几个对象
我们首先学习一下Directsound中常用的几个对象,简单学习一下哦DirectSound其实很简单的,主要有下面常用的几个对象。
对象 | 数量 | 作用 | 主要接口 |
设备对象 | 每个应用程序只有一个设备对象 | 用来管理设备,创建辅助缓冲区 | IDirectSound8 |
辅助缓冲区对象 | 每一个声音对应一个辅助缓冲区,可以有多个辅助缓冲区 | 用来管理一个静态的或者动态的声音流,然后在主缓冲区中混音 | IDirectSoundBuffer8, IDirectSound3DBuffer8, IDirectSoundNotify8 |
主缓冲区对象 | 一个应用程序只有一个主缓冲区 | 将辅助缓冲区的数据进行混音,并且控制3D参数. | IDirectSoundBuffer, IDirectSound3DListener8 |
特技对象 | 没有 | 来辅助缓冲的声音数据进行处理 | 8个特技接口IDirectSoundFXChorus8 |
第一步,创建一个设备对象,设置设备对象的协作度。
在你的代码中你可以通过调用DirectSoundCreat8函数来创建一个支持IDirectSound8接口的对象,这个对象通常代表缺省的播放设备。当然你可以枚举可用的设备,然后将设备的GUID传递给DirectSoundCreat8函数。
如果没有声音输出设备,这个函数就返回error,或者,在VXD驱动程序下,如果声音输出设备正被某个应用程序通过waveform格式的api函数所控制,该函数也返回error。
下面是创建对象的代码,及其简单
LPDIRECTSOUND8 lpDirectSound; HRESULT hr = DirectSoundCreate8(NULL, & lpDirectSound, NULL)); |
注意,Directsound虽然基于COM,但是你并不需要初始化com库,这些Directsound都帮你做好了,当然,如果你使用DMOs特技,你就要自己初始化com库了,切记。
因为Windows是一个多任务操作环境,在同一个时刻有可能多个应用程序共用同一个设备,通过协作水平,DirectX就可以保证这些应用程序在访问设备的时候不会冲突,每个Directsound应用程序都有一个协作度,用来确定来接近设备的程度,当你创建完设备对象后,一定要调用IDirectSound8::SetCooperativeLevel来设置协作度,否则,你不会听到声音的。
HRESULT hr = lpDirectSound->SetCooperativeLevel(hwnd, DSSCL_PRIORITY); if (FAILED(hr)) { ErrorHandler(hr); // Add error-handling here. } |
第二步,创建一个辅助Buffer,也叫后备缓冲区
你可以通过IDirectSound8::CreateSoundBuffer来创建buffer对象,这个对象主要用来获取处理数据,这种buffer称作辅助缓冲区,以和主缓冲区区别开来,Direcsound通过把几个后备缓冲区的声音混合到主缓冲区中,然后输出到声音输出设备上,达到混音的效果。
第三步,获取PCM类型的数据
将WAV文件或者其他资源的数据读取到缓冲区中。
第四步,将数据读取到缓冲区
你可以通过 IDirectSoundBuffer8::Lock.方法来准备一个辅助缓冲区来进行写操作,通常这个方法返回一个内存地址,见数据从你的私人buffer中复制到这个地址中,然后调用IDirectSoundBuffer8::Unlock.
第五步,播放缓冲区中的数据
你可以通过IDirectSoundBuffer8::Play方法来播放缓冲区中的音频数据,你可以通过IDirectSoundBuffer8::Stop来暂停播放数据,你可以反复的莱停止,播放,音频数据,如果你同时创建了几个buffer,那么你就可以同时来播放这些数据,这些声音会自动进行混音的。
你可以通过IDirectSoundBuffer8::GetVolume and IDirectSoundBuffer8::SetVolume函数来获取或者设置正在播放的音频的音量的大小。
如果设置主缓冲区的音量就会改变声卡的音频的声量大小。音量的大小,用分贝来表示,一般没法来增强缺省的音量,这里要提示一下,分贝的增减不是线形的,减少3分贝相当于减少1/2的能量。最大值衰减100分贝几乎听不到了。
通过IDirectSoundBuffer8::GetFrequency and IDirectSoundBuffer8::SetFrequency方法可以获取设置音频播放的频率,主缓冲区的频率不允许改动,通过 IDirectSoundBuffer8::GetPan and IDirectSoundBuffer8::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工作。 HRESULT hr; 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); DSBUFFERDESC dsbd; 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; LPDIRECTSOUNDBUFFER lpbuffer; //创建辅助缓冲区对象 if(FAILED(hr = g_pDsd->CreateSoundBuffer(&dsbd,&lpbuffer,NULL))) return ; if( FAILED( hr = lpbuffer->QueryInterface( IID_IDirectSoundBuffer8, (LPVOID*) &g_pDSBuffer8) ) ) return ; lpbuffer->Release(); //准备工作做完了,下面就开始播放了 LPVOID lplockbuf; DWORD len; DWORD dwWrite; 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); |
HANDLE g_event[MAX_AUDIO_BUF]; for(int i =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(); |
#define MAX_AUDIO_BUF 4 #define BUFFERNOTIFYSIZE 1920 LPDIRECTSOUNDBUFFER8 g_pDSBuffer8 = NULL; //buffer LPDIRECTSOUND8 g_pDsd = NULL; //dsound CWaveFile *g_pWaveFile= NULL; BOOL g_bPlaying = FALSE; //是否正在播放 LPDIRECTSOUNDNOTIFY8 g_pDSNotify = NULL; DSBPOSITIONNOTIFY g_aPosNotify[MAX_AUDIO_BUF];//设置通知标志的数组 HANDLE g_event[MAX_AUDIO_BUF]; DWORD g_dwNextWriteOffset = 0; //初始化DirectSound HRESULT hr; 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); 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; LPDIRECTSOUNDBUFFER lpbuffer; //创建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(int i =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(); ok,在下面的play函数中,我们就要单独启动一个线程,来播放了 void OnBnClickedButtonPlay() { g_bPlaying =TRUE; g_pWaveFile->ResetFile(); CreateThread(0,0,PlayThread,this,NULL,NULL); } //停止播放音频 void CDsoundEffectDemoDlg::OnBnClickedButtonStop() { // TODO: 在此添加控件通知处理程序代码 g_bPlaying =FALSE; Sleep(500); g_pDSBuffer8->Stop(); } |
DWORD WINAPI PlayThread(LPVOID lpParame) { DWORD res; LPVOID lplockbuf; DWORD len; DWORD dwWrite; 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(); } return 0; } |
void ProcessBuffer() { DWORD dwBytesWrittenToBuffer = 0; VOID* pDSLockedBuffer = NULL; VOID* pDSLockedBuffer2 = NULL; DWORD dwDSLockedBufferSize; DWORD dwDSLockedBufferSize2; HRESULT hr; 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)) { //write 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); } } |