最簡單的視音頻播放示例8:DirectSound播放PCM
本文記錄DirectSound播放音頻的技術。DirectSound是Windows下最常見的音頻播放技術。目前大部分的音頻播放應用都是通過DirectSound來播放的。本文記錄一個使用DirectSound播放PCM的例子。
注:一位仁兄已經提醒我DirectSound已經計劃被XAudio2取代了。后來考證了一下發現確有此事。因此在下次更新中考慮加入XAudio2播放PCM的例子。本文仍然記錄一下DirectSound這位“元老”。
DirectSound簡介
DirectSound是微軟所開發DirectX的組件之一,可以在Windows 操作系統上錄音,并且記錄波形音效(waveform sound)。目前DirectSound 是一個成熟的API ,提供許多有用的功能,例如能夠在較高的分辨率播放多聲道聲音。
DirectSound3D(DS3D)最早是1993年與 DirectX 3 一起發表的。DirectX 8以后的DirectSound和DirectSound3D的(DS3D)被合稱DirectX Audio。
DirectSound有以下幾種對象:
對象
數量
作用
主要接口
設備
每個應用程序只有一個設備對象
用來管理設備,創建輔助緩沖區
IDirectSound8
輔助緩沖區
每一個聲音對應一個輔助緩沖區
用來管理一個靜態的或者動態的聲音流,然后在主緩沖區中混音
IDirectSoundBuffer8,
IDirectSound3DBuffer8,
IDirectSoundNotify8
主緩沖區
一個應用程序只有一個主緩沖區
將輔助緩沖區的數據進行混音,并且控制3D參數.
IDirectSoundBuffer,
IDirectSound3DListener8
DirectSound播放音頻的流程
使用DirectSound播放音頻一般情況下需要如下步驟:
1. 初始化
1) 創建一個IDirectSound8接口的對象
2) 設置協作級
3) 創建一個主緩沖對象
4) 創建一個副緩沖對象
5) 創建通知對象
6) 設置通知位置
7) 開始播放
2. 循環播放聲音
1) 數據填充至副緩沖區
2) 等待播放完成
下面結合詳細分析一下上文的流程。
1. 初始化
1) 創建一個IDirectSound8接口的對象
通過DirectSoundCreate8()方法可以創建一個設備對象。這個對象通常代表缺省的播放設備。DirectSoundCreate8()函數原型如下。

- HRESULT DirectSoundCreate8(
- LPCGUID lpcGuidDevice,
- LPDIRECTSOUND8 * ppDS8,
- LPUNKNOWN pUnkOuter
- )
參數的含義如下:
lpcGuidDevice:要創建的設備對象的GUID。可以指定為NULL,代表默認的播放設備。
ppDS8:返回的IDirectSound8對象的地址。
pUnkOuter:必須設為NULL。
例如如下代碼即可創建一個IDirectSound8接口的對象

- IDirectSound8 *m_pDS=NULL;
- DirectSoundCreate8(NULL,&m_pDS,NULL);
2) 設置協作級
Windows 是一個多任務環境,同一時間有多個應用程序去訪問設備。通過使用協作級別,DirectSound可以確保應用程序不會在別的設備使用時去訪問,每個 DirectSound應用程序都有一個協作級別,這個級別決定著訪問硬件的權限。
在創建一個設備對象以后,必須通過用IDirectSound8的SetCooperativeLevel()設置協作權限,否則將聽不到聲音。SetCooperativeLevel()的原型如下

- HRESULT SetCooperativeLevel(
- HWND hwnd,
- DWORD dwLevel
- )
參數的含義如下:
hwnd:應用程序窗口句柄。
dwLevel:支持以下幾種級別。
DSSCL_EXCLUSIVE:與DSSCL_PRIORITY具有相同的作用。
DSSCL_NORMAL:正常的協調層級標志,其他程序可共享聲卡設備進行播放。
DSSCL_PRIORITY:設置聲卡設備為當前程序獨占。
DSSCL_WRITEPRIMAR:可寫主緩沖區,此時副緩沖區就不能進行播放處理,即不能將次緩沖區的數據送進混聲器,再輸出到主緩沖區上。這是最完全控制聲音播放的方式。
3) 創建一個主緩沖對象
使用IDirectSound8的CreateSoundBuffer()可以創建一個IDirectSoundBuffer接口的主緩沖區對象。CreateSoundBuffer()的原型如下。

- HRESULT CreateSoundBuffer(
- LPCDSBUFFERDESC pcDSBufferDesc,
- LPDIRECTSOUNDBUFFER * ppDSBuffer,
- LPUNKNOWN pUnkOuter
- )
參數的含義如下:
pcDSBufferDesc:描述聲音緩沖的DSBUFFERDESC結構體的地址
ppDSBuffer:返回的IDirectSoundBuffer接口的對象的地址。
pUnkOuter:必須設置為NULL。
其中涉及到一個描述聲音緩沖的結構體DSBUFFERDESC,該結構體的定義如下:

- typedef struct _DSBUFFERDESC
- {
- DWORD dwSize;
- DWORD dwFlags;
- DWORD dwBufferBytes;
- DWORD dwReserved;
- LPWAVEFORMATEX lpwfxFormat;
- } DSBUFFERDESC
簡單解釋一下其中的變量的含義:
dwSize:結構體的大小。必須初始化該值。
dwFlags:設置聲音緩存的屬性。有很多選項,可以組合使用,就不一一列出了。詳細的參數可以查看文檔。
dwBufferBytes:緩沖的大小。
dwReserved:保留參數,暫時沒有用。
lpwfxFormat:指向一個WAVE格式文件頭的指針。
設置DSBUFFERDESC完畢后,就可以使用CreateSoundBuffer()創建主緩沖了。示例代碼如下:

- DSBUFFERDESC dsbd;
- memset(&dsbd,0,sizeof(dsbd));
- dsbd.dwSize=sizeof(dsbd);
- dsbd.dwFlags=DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
- dsbd.dwBufferBytes=MAX_AUDIO_BUF*BUFFERNOTIFYSIZE;
- //WAVE Header
- dsbd.lpwfxFormat=(WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
- dsbd.lpwfxFormat->wFormatTag=WAVE_FORMAT_PCM;
- /* format type */
- (dsbd.lpwfxFormat)->nChannels=channels;
- /* number of channels (i.e. mono, stereo...) */
- (dsbd.lpwfxFormat)->nSamplesPerSec=sample_rate;
- /* sample rate */
- (dsbd.lpwfxFormat)->nAvgBytesPerSec=sample_rate*(bits_per_sample/8)*channels;
- /* for buffer estimation */
- (dsbd.lpwfxFormat)->nBlockAlign=(bits_per_sample/8)*channels;
- /* block size of data */
- (dsbd.lpwfxFormat)->wBitsPerSample=bits_per_sample;
- /* number of bits per sample of mono data */
- (dsbd.lpwfxFormat)->cbSize=0;
- //Creates a sound buffer object to manage audio samples.
- HRESULT hr1;
- if( FAILED(m_pDS->CreateSoundBuffer(&dsbd,&m_pDSBuffer,NULL))){
- return FALSE;
- }
4) 創建一個副緩沖對象
使用IDirectSoundBuffer的QueryInterface()可以得到一個IDirectSoundBuffer8接口的對象。IDirectSoundBuffer8的GUID為IID_IDirectSoundBuffer8。示例代碼如下。

- IDirectSoundBuffer *m_pDSBuffer=NULL;
- IDirectSoundBuffer8 *m_pDSBuffer8=NULL;
- ...
- if( FAILED(m_pDSBuffer->QueryInterface(IID_IDirectSoundBuffer8,(LPVOID*)&m_pDSBuffer8))){
- return FALSE ;
- }
5) 創建通知對象
使用IDirectSoundBuffer8的QueryInterface()可以得到一個IDirectSoundNotify8接口的對象。IDirectSoundBuffer8的GUID為IID_IDirectSoundNotify。示例代碼如下。

- IDirectSoundBuffer8 *m_pDSBuffer8=NULL;
- IDirectSoundNotify8 *m_pDSNotify=NULL;
- …
- if(FAILED(m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID*)&m_pDSNotify))){
- return FALSE ;
- }
一句話概括一下通知對象的作用:當DirectSound緩沖區中的數據播放完畢后,告知系統應該填充新的數據。
6) 設置通知位置
使用IDirectSoundNotify8的SetNotificationPositions()可以設置通知的位置。SetNotificationPositions()的原型如下。

- HRESULT SetNotificationPositions(
- DWORD dwPositionNotifies,
- LPCDSBPOSITIONNOTIFY pcPositionNotifies
- )
參數含義如下。
dwPositionNotifies:DSBPOSITIONNOTIFY結構體的數量。既包含幾個通知的位置。
pcPositionNotifies:指向DSBPOSITIONNOTIFY結構體數組的指針。
再這里涉及到一個結構體DSBPOSITIONNOTIFY,它描述了通知的位置。DSBPOSITIONNOTIFY的定義如下。

- typedef struct DSBPOSITIONNOTIFY {
- DWORD dwOffset;
- HANDLE hEventNotify;
- } DSBPOSITIONNOTIFY;
它的成員的含義如下。
dwOffset:通知事件觸發的位置(距離緩沖開始位置的偏移量)。
hEventNotify:觸發的事件的句柄。
7) 開始播放
使用IDirectSoundBuffer8的SetCurrentPosition ()可以設置播放的位置。SetCurrentPosition ()原型如下

- HRESULT SetCurrentPosition(
- DWORD dwNewPosition
- )
其中dwNewPosition是播放點與緩沖區首個字節之間的偏移量。
使用IDirectSoundBuffer8的Play ()可以開始播放音頻數據。Play ()原型如下。

- HRESULT Play(
- DWORD dwReserved1,
- DWORD dwPriority,
- DWORD dwFlags
- )
參數含義:
dwReserved1:保留參數,必須取0。
dwPriority:優先級,一般情況下取0即可。
dwFlags:標志位。目前常見的是DSBPLAY_LOOPING。當播放至緩沖區結尾的時候,重新從緩沖區開始處開始播放。
2. 循環播放聲音
1) 數據填充至副緩沖區
數據填充至副緩沖區之前,需要先使用Lock()鎖定緩沖區。然后就可以使用fread(),memcpy()等方法將PCM音頻采樣數據填充至緩沖區。數據填充完畢后,使用Unlock()取消對緩沖區的鎖定。
Lock()函數的原型如下。

- HRESULT Lock(
- DWORD dwOffset,
- DWORD dwBytes,
- LPVOID * ppvAudioPtr1,
- LPDWORD pdwAudioBytes1,
- LPVOID * ppvAudioPtr2,
- LPDWORD pdwAudioBytes2,
- DWORD dwFlags
- )
參數的含義如下。
dwOffset:鎖定的內存與緩沖區首地址之間的偏移量。
dwBytes:鎖定的緩存的大小。
ppvAudioPtr1:獲取到的指向緩存數據的指針。
pdwAudioBytes1:獲取到的緩存數據的大小。
ppvAudioPtr2:沒有用到,設置為NULL。
pdwAudioBytes2:沒有用到,設置為0。
dwFlags:暫時沒有研究。
UnLock()函數的原型如下。

- HRESULT Unlock(
- LPVOID pvAudioPtr1,
- DWORD dwAudioBytes1,
- LPVOID pvAudioPtr2,
- DWORD dwAudioBytes2
- )
參數含義如下。
pvAudioPtr1:通過Lock()獲取到的指向緩存數據的指針。
dwAudioBytes1:寫入的數據量。
pvAudioPtr2:沒有用到。
dwAudioBytes2:沒有用到。
2) 等待播放完成
根據此前設置的通知機制,使用WaitForMultipleObjects()等待緩沖區中的數據播放完畢,然后進入下一個循環。
播放音頻流程總結
DirectSound播放PCM音頻數據的流程如下圖所示。
其中涉及到的幾個結構體之間的關系如下圖所示。
代碼
貼上源代碼。
[cpp] view plaincopy
- /**
- * 最簡單的DirectSound播放音頻的例子(DirectSound播放PCM)
- * Simplest Audio Play DirectSound (DirectSound play PCM)
- *
- * 雷霄驊 Lei Xiaohua
- * leixiaohua1020@126.com
- * 中國傳媒大學/數字電視技術
- * Communication University of China / Digital TV Technology
- * http://blog.csdn.net/leixiaohua1020
- *
- * 本程序使用DirectSound播放PCM音頻采樣數據。
- * 是最簡單的DirectSound播放音頻的教程。
- *
- * 函數調用步驟如下:
- *
- * [初始化]
- * DirectSoundCreate8(): 創建一個DirectSound對象。
- * SetCooperativeLevel(): 設置協作權限,不然沒有聲音。
- * IDirectSound8->CreateSoundBuffer(): 創建一個主緩沖區對象。
- * IDirectSoundBuffer->QueryInterface(IID_IDirectSoundBuffer8..):
- * 創建一個副緩沖區對象,用來存儲要播放的聲音數據文件。
- * IDirectSoundBuffer8->QueryInterface(IID_IDirectSoundNotify..):
- * 創建通知對象,通知應用程序指定播放位置已經達到。
- * IDirectSoundNotify8->SetNotificationPositions(): 設置通知位置。
- * IDirectSoundBuffer8->SetCurrentPosition(): 設置播放的起始點。
- * IDirectSoundBuffer8->Play(): 開始播放。
- *
- * [循環播放數據]
- * IDirectSoundBuffer8->Lock(): 鎖定副緩沖區,準備寫入數據。
- * fread(): 讀取數據。
- * IDirectSoundBuffer8->Unlock(): 解鎖副緩沖區。
- * WaitForMultipleObjects(): 等待“播放位置已經達到”的通知。
- *
- * This software plays PCM raw audio data using DirectSound.
- * It's the simplest tutorial about DirectSound.
- *
- * The process is shown as follows:
- *
- * [Init]
- * DirectSoundCreate8(): Init DirectSound object.
- * SetCooperativeLevel(): Must set, or we won't hear sound.
- * IDirectSound8->CreateSoundBuffer(): Create primary sound buffer.
- * IDirectSoundBuffer->QueryInterface(IID_IDirectSoundBuffer8..):
- * Create secondary sound buffer.
- * IDirectSoundBuffer8->QueryInterface(IID_IDirectSoundNotify..):
- * Create Notification object.
- * IDirectSoundNotify8->SetNotificationPositions():
- * Set Notification Positions.
- * IDirectSoundBuffer8->SetCurrentPosition(): Set position to start.
- * IDirectSoundBuffer8->Play(): Begin to play.
- *
- * [Loop to play data]
- * IDirectSoundBuffer8->Lock(): Lock secondary buffer.
- * fread(): get PCM data.
- * IDirectSoundBuffer8->Unlock(): UnLock secondary buffer.
- * WaitForMultipleObjects(): Wait for Notifications.
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <windows.h>
- #include <dsound.h>
- #define MAX_AUDIO_BUF 4
- #define BUFFERNOTIFYSIZE 192000
- int sample_rate=44100; //PCM sample rate
- int channels=2; //PCM channel number
- int bits_per_sample=16; //bits per sample
- BOOL main(int argc,char * argv[])
- {
- int i;
- FILE * fp;
- if((fp=fopen("../NocturneNo2inEflat_44.1k_s16le.pcm","rb"))==NULL){
- printf("cannot open this file\n");
- return -1;
- }
- IDirectSound8 *m_pDS=NULL;
- IDirectSoundBuffer8 *m_pDSBuffer8=NULL; //used to manage sound buffers.
- IDirectSoundBuffer *m_pDSBuffer=NULL;
- IDirectSoundNotify8 *m_pDSNotify=NULL;
- DSBPOSITIONNOTIFY m_pDSPosNotify[MAX_AUDIO_BUF];
- HANDLE m_event[MAX_AUDIO_BUF];
- SetConsoleTitle(TEXT("Simplest Audio Play DirectSound"));//Console Title
- //Init DirectSound
- if(FAILED(DirectSoundCreate8(NULL,&m_pDS,NULL)))
- return FALSE;
- if(FAILED(m_pDS->SetCooperativeLevel(FindWindow(NULL,TEXT("Simplest Audio Play DirectSound")),DSSCL_NORMAL)))
- return FALSE;
- DSBUFFERDESC dsbd;
- memset(&dsbd,0,sizeof(dsbd));
- dsbd.dwSize=sizeof(dsbd);
- dsbd.dwFlags=DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
- dsbd.dwBufferBytes=MAX_AUDIO_BUF*BUFFERNOTIFYSIZE;
- //WAVE Header
- dsbd.lpwfxFormat=(WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
- dsbd.lpwfxFormat->wFormatTag=WAVE_FORMAT_PCM;
- /* format type */
- (dsbd.lpwfxFormat)->nChannels=channels;
- /* number of channels (i.e. mono, stereo...) */
- (dsbd.lpwfxFormat)->nSamplesPerSec=sample_rate;
- /* sample rate */
- (dsbd.lpwfxFormat)->nAvgBytesPerSec=sample_rate*(bits_per_sample/8)*channels;
- /* for buffer estimation */
- (dsbd.lpwfxFormat)->nBlockAlign=(bits_per_sample/8)*channels;
- /* block size of data */
- (dsbd.lpwfxFormat)->wBitsPerSample=bits_per_sample;
- /* number of bits per sample of mono data */
- (dsbd.lpwfxFormat)->cbSize=0;
- //Creates a sound buffer object to manage audio samples.
- HRESULT hr1;
- if( FAILED(m_pDS->CreateSoundBuffer(&dsbd,&m_pDSBuffer,NULL))){
- return FALSE;
- }
- if( FAILED(m_pDSBuffer->QueryInterface(IID_IDirectSoundBuffer8,(LPVOID*)&m_pDSBuffer8))){
- return FALSE ;
- }
- //Get IDirectSoundNotify8
- if(FAILED(m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID*)&m_pDSNotify))){
- return FALSE ;
- }
- for(i =0;i<MAX_AUDIO_BUF;i++){
- m_pDSPosNotify[i].dwOffset =i*BUFFERNOTIFYSIZE;
- m_event[i]=::CreateEvent(NULL,false,false,NULL);
- m_pDSPosNotify[i].hEventNotify=m_event[i];
- }
- m_pDSNotify->SetNotificationPositions(MAX_AUDIO_BUF,m_pDSPosNotify);
- m_pDSNotify->Release();
- //Start Playing
- BOOL isPlaying =TRUE;
- LPVOID buf=NULL;
- DWORD buf_len=0;
- DWORD res=WAIT_OBJECT_0;
- DWORD offset=BUFFERNOTIFYSIZE;
- m_pDSBuffer8->SetCurrentPosition(0);
- m_pDSBuffer8->Play(0,0,DSBPLAY_LOOPING);
- //Loop
- while(isPlaying){
- if((res >=WAIT_OBJECT_0)&&(res <=WAIT_OBJECT_0+3)){
- m_pDSBuffer8->Lock(offset,BUFFERNOTIFYSIZE,&buf,&buf_len,NULL,NULL,0);
- if(fread(buf,1,buf_len,fp)!=buf_len){
- //File End
- //Loop:
- fseek(fp, 0, SEEK_SET);
- fread(buf,1,buf_len,fp);
- //Close:
- //isPlaying=0;
- }
- m_pDSBuffer8->Unlock(buf,buf_len,NULL,0);
- offset+=buf_len;
- offset %= (BUFFERNOTIFYSIZE * MAX_AUDIO_BUF);
- printf("this is %7d of buffer\n",offset);
- }
- res = WaitForMultipleObjects (MAX_AUDIO_BUF, m_event, FALSE, INFINITE);
- }
- return 0;
- }
運行結果
代碼運行之后,會彈出一個“控制臺”對話框如下圖所示。同時音頻設備里面可以聽到播放的聲音。
下載
代碼位于“Simplest Media Play”中
SourceForge項目地址:https://sourceforge.net/projects/simplestmediaplay/
CSDN下載地址:http://download.csdn.net/detail/leixiaohua1020/8054395
上述工程包含了使用各種API(Direct3D,OpenGL,GDI,DirectSound,SDL2)播放多媒體例子。其中音頻輸入為PCM采樣數據。輸出至系統的聲卡播放出來。視頻輸入為YUV/RGB像素數據。輸出至顯示器上的一個窗口播放出來。
通過本工程的代碼初學者可以快速學習使用這幾個API播放視頻和音頻的技術。
一共包括了如下幾個子工程:
simplest_audio_play_directsound: 使用DirectSound播放PCM音頻采樣數據。
simplest_audio_play_sdl2: 使用SDL2播放PCM音頻采樣數據。
simplest_video_play_direct3d: 使用Direct3D的Surface播放RGB/YUV視頻像素數據。
simplest_video_play_direct3d_texture:使用Direct3D的Texture播放RGB視頻像素數據。
simplest_video_play_gdi: 使用GDI播放RGB/YUV視頻像素數據。
simplest_video_play_opengl: 使用OpenGL播放RGB/YUV視頻像素數據。
simplest_video_play_opengl_texture: 使用OpenGL的Texture播放YUV視頻像素數據。
simplest_video_play_sdl2: 使用SDL2播放RGB/YUV視頻像素數據。
from:http://blog.csdn.net/leixiaohua1020/article/details/40540147
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成