DirectSound入門基礎介紹 和 環境的配置
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播放音訊檔案的兩種方法。