DirectSound播放PCM(可播放實時采集的音頻數據)


前言

  該篇整理的原始來源為http://blog.csdn.net/leixiaohua1020/article/details/40540147。非常感謝該博主的無私奉獻,寫了不少關於不同多媒體庫的博文。讓我這個小白學習到不少。現在將其整理是為了收錄,以備自己查看。

一、DirectSound簡介

  DirectSound是微軟所開發DirectX的組件之一,可以在Windows 操作系統上錄音,並且記錄波形音效(waveform sound)。目前DirectSound 是一個成熟的API ,提供許多有用的功能,例如能夠在較高的分辨率播放多聲道聲音。DirectSound3D(DS3D)最早是1993年與 DirectX 3 一起發表的。DirectX 8以后的DirectSound和DirectSound3D的(DS3D)被合稱DirectX Audio。

  DirectSound有以下幾種對象:

圖1.DirectSound對象

二、DirectSound播放音頻的流程

使用DirectSound播放音頻一般情況下需要如下步驟:

1.初始化

  • 創建一個IDirectSound8接口的對象
  • 設置協作級
  • 創建一個主緩沖對象
  • 創建一個副緩沖對象
  • 創建通知對象
  • 設置通知位置
  • 開始播放

2.循環播放聲音

  • 數據填充至副緩沖區
  • 等待播放完成

三、結合接口詳細分析

1.初始化

1)創建一個IDirectSound8接口的對象

  通過DirectSoundCreate8()方法可以創建一個設備對象。這個對象通常代表缺省的播放設備。DirectSoundCreate8()函數原型如下。

1 HRESULT DirectSoundCreate8(
2      LPCGUID lpcGuidDevice,
3      LPDIRECTSOUND8 * ppDS8,
4      LPUNKNOWN pUnkOuter
5 )

參數的含義如下:

lpcGuidDevice:要創建的設備對象的GUID。可以指定為NULL,代表默認的播放設備。
ppDS8:返回的IDirectSound8對象的地址。
pUnkOuter:必須設為NULL。

例如如下代碼即可創建一個IDirectSound8接口的對象

1 IDirectSound8 *m_pDS=NULL;    
2 DirectSoundCreate8(NULL,&m_pDS,NULL);

2) 設置協作級

  Windows 是一個多任務環境,同一時間有多個應用程序去訪問設備。通過使用協作級別,DirectSound可以確保應用程序不會在別的設備使用時去訪問,每個 DirectSound應用程序都有一個協作級別,這個級別決定着訪問硬件的權限。

  在創建一個設備對象以后,必須通過用IDirectSound8的SetCooperativeLevel()設置協作權限,否則將聽不到聲音。SetCooperativeLevel()的原型如下

1 HRESULT SetCooperativeLevel(
2  HWND hwnd,
3  DWORD dwLevel
4 )

參數的含義如下:

hwnd:應用程序窗口句柄。
dwLevel:支持以下幾種級別:
DSSCL_EXCLUSIVE:與DSSCL_PRIORITY具有相同的作用。
DSSCL_NORMAL:正常的協調層級標志,其他程序可共享聲卡設備進行播放。
DSSCL_PRIORITY:設置聲卡設備為當前程序獨占。
DSSCL_WRITEPRIMAR:可寫主緩沖區,此時副緩沖區就不能進行播放處理,即不能將次緩沖區的數據送進混聲器,再輸出到主緩沖區上。這是最完全控制聲音播放的方式。

 3) 創建一個主緩沖對象

  使用IDirectSound8的CreateSoundBuffer()可以創建一個IDirectSoundBuffer接口的主緩沖區對象。CreateSoundBuffer()的原型如下。

1 HRESULT CreateSoundBuffer(
2  LPCDSBUFFERDESC pcDSBufferDesc,
3  LPDIRECTSOUNDBUFFER * ppDSBuffer,
4  LPUNKNOWN pUnkOuter
5 )

 

參數的含義如下:
pcDSBufferDesc:描述聲音緩沖的DSBUFFERDESC結構體的地址
ppDSBuffer:返回的IDirectSoundBuffer接口的對象的地址。
pUnkOuter:必須設置為NULL。

  其中涉及到一個描述聲音緩沖的結構體DSBUFFERDESC,該結構體的定義如下:

1 typedef struct _DSBUFFERDESC
2 {
3     DWORD           dwSize;
4     DWORD           dwFlags;
5     DWORD           dwBufferBytes;
6     DWORD           dwReserved;
7     LPWAVEFORMATEX  lpwfxFormat;
8 } DSBUFFERDESC

簡單解釋一下其中的變量的含義:
dwSize:結構體的大小。必須初始化該值。
dwFlags:設置聲音緩存的屬性。有很多選項,可以組合使用,就不一一列出了。詳細的參數可以查看文檔。
dwBufferBytes:緩沖的大小。
dwReserved:保留參數,暫時沒有用。
lpwfxFormat:指向一個WAVE格式文件頭的指針。

  設置DSBUFFERDESC完畢后,就可以使用CreateSoundBuffer()創建主緩沖了。示例代碼如下:

 1     DSBUFFERDESC dsbd;
 2     memset(&dsbd,0,sizeof(dsbd));
 3     dsbd.dwSize=sizeof(dsbd);
 4     dsbd.dwFlags=DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
 5     dsbd.dwBufferBytes=MAX_AUDIO_BUF*BUFFERNOTIFYSIZE; 
 6     //WAVE Header
 7     dsbd.lpwfxFormat=(WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
 8     dsbd.lpwfxFormat->wFormatTag=WAVE_FORMAT_PCM;   
 9     /* format type */
10     (dsbd.lpwfxFormat)->nChannels=channels;          
11     /* number of channels (i.e. mono, stereo...) */
12     (dsbd.lpwfxFormat)->nSamplesPerSec=sample_rate;     
13     /* sample rate */
14     (dsbd.lpwfxFormat)->nAvgBytesPerSec=sample_rate*(bits_per_sample/8)*channels; 
15     /* for buffer estimation */
16     (dsbd.lpwfxFormat)->nBlockAlign=(bits_per_sample/8)*channels;        
17     /* block size of data */
18     (dsbd.lpwfxFormat)->wBitsPerSample=bits_per_sample;     
19     /* number of bits per sample of mono data */
20     (dsbd.lpwfxFormat)->cbSize=0;
21 
22 
23     //Creates a sound buffer object to manage audio samples. 
24     HRESULT hr1;
25     if( FAILED(m_pDS->CreateSoundBuffer(&dsbd,&m_pDSBuffer,NULL))){   
26         return FALSE;
27     }

4) 創建一個副緩沖對象

  使用IDirectSoundBuffer的QueryInterface()可以得到一個IDirectSoundBuffer8接口的對象。IDirectSoundBuffer8的GUID為IID_IDirectSoundBuffer8。示例代碼如下。

1 IDirectSoundBuffer *m_pDSBuffer=NULL;
2 IDirectSoundBuffer8 *m_pDSBuffer8=NULL;
3 ...
4 if( FAILED(m_pDSBuffer->QueryInterface(IID_IDirectSoundBuffer8,(LPVOID*)&m_pDSBuffer8))){
5     return FALSE ;
6 }

5) 創建通知對象

  使用IDirectSoundBuffer8的QueryInterface()可以得到一個IDirectSoundNotify8接口的對象。IDirectSoundBuffer8的GUID為IID_IDirectSoundNotify。示例代碼如下。

1 IDirectSoundBuffer8 *m_pDSBuffer8=NULL;
2 IDirectSoundNotify8 *m_pDSNotify=NULL;    
3 4 if(FAILED(m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID*)&m_pDSNotify))){
5     return FALSE ;
6 }

  一句話概括一下通知對象的作用:當DirectSound緩沖區中的數據播放完畢后,告知系統應該填充新的數據。

6) 設置通知位置

  使用IDirectSoundNotify8的SetNotificationPositions()可以設置通知的位置。SetNotificationPositions()的原型如下。

1 HRESULT SetNotificationPositions(
2          DWORD dwPositionNotifies,
3          LPCDSBPOSITIONNOTIFY pcPositionNotifies
4 )

參數含義如下。
dwPositionNotifies:DSBPOSITIONNOTIFY結構體的數量。既包含幾個通知的位置。
pcPositionNotifies:指向DSBPOSITIONNOTIFY結構體數組的指針。

  在這里涉及到一個結構體DSBPOSITIONNOTIFY,它描述了通知的位置。DSBPOSITIONNOTIFY的定義如下。

1 typedef struct DSBPOSITIONNOTIFY {
2     DWORD dwOffset;
3     HANDLE hEventNotify;
4 } DSBPOSITIONNOTIFY;

它的成員的含義如下。
dwOffset:通知事件觸發的位置(距離緩沖開始位置的偏移量)。
hEventNotify:觸發的事件的句柄。

7) 開始播放

  使用IDirectSoundBuffer8的SetCurrentPosition ()可以設置播放的位置。SetCurrentPosition ()原型如下

1 HRESULT SetCurrentPosition(
2          DWORD dwNewPosition
3 )

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

1 HRESULT Play(
2          DWORD dwReserved1,
3          DWORD dwPriority,
4          DWORD dwFlags
5 )

參數含義:
dwReserved1:保留參數,必須取0。
dwPriority:優先級,一般情況下取0即可。
dwFlags:標志位。目前常見的是DSBPLAY_LOOPING。當播放至緩沖區結尾的時候,重新從緩沖區開始處開始播放。

 

2. 循環播放聲音

1) 數據填充至副緩沖區

 

  數據填充至副緩沖區之前,需要先使用Lock()鎖定緩沖區。然后就可以使用fread(),memcpy()等方法將PCM音頻采樣數據填充至緩沖區。數據填充完畢后,使用Unlock()取消對緩沖區的鎖定。如果是實時采集的音頻數據,只要將音頻數據復制到Lock()獲取到的ppvAudioPtr1指向的地址,大小為pdwAudioBytes1,就可以播放了。(我使用的方式就是如此,實現了實時音頻的播放,下文中的例子數據是讀取自文件。)

  Lock()函數的原型如下。

1 HRESULT Lock(
2          DWORD dwOffset,
3          DWORD dwBytes,
4          LPVOID * ppvAudioPtr1,
5          LPDWORD  pdwAudioBytes1,
6          LPVOID * ppvAudioPtr2,
7          LPDWORD pdwAudioBytes2,
8          DWORD dwFlags
9 )

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

  UnLock()函數的原型如下。

1 HRESULT Unlock(
2          LPVOID pvAudioPtr1,
3          DWORD dwAudioBytes1,
4          LPVOID pvAudioPtr2,
5          DWORD dwAudioBytes2
6 )

參數含義如下。
pvAudioPtr1:通過Lock()獲取到的指向緩存數據的指針。
dwAudioBytes1:寫入的數據量。
pvAudioPtr2:沒有用到。
dwAudioBytes2:沒有用到。

2) 等待播放完成

  根據此前設置的通知機制,使用WaitForMultipleObjects()等待緩沖區中的數據播放完畢,然后進入下一個循環。

四、播放音頻流程總結

  DirectSound播放PCM音頻數據的流程如下圖所示。

圖2

  其中涉及到的幾個結構體之間的關系如下圖所示。

圖3.結構體關系

五、使用示例代碼

  該代碼也是直接使用的來自原博主的代碼,如下

  1 /**
  2  * 最簡單的DirectSound播放音頻的例子(DirectSound播放PCM)
  3  * Simplest Audio Play DirectSound (DirectSound play PCM) 
  4  *
  5  * 雷霄驊 Lei Xiaohua
  6  * leixiaohua1020@126.com
  7  * 中國傳媒大學/數字電視技術
  8  * Communication University of China / Digital TV Technology
  9  * http://blog.csdn.net/leixiaohua1020
 10  *
 11  * 本程序使用DirectSound播放PCM音頻采樣數據。
 12  * 是最簡單的DirectSound播放音頻的教程。
 13  *
 14  * 函數調用步驟如下:
 15  *
 16  * [初始化]
 17  * DirectSoundCreate8():創建一個DirectSound對象。
 18  * SetCooperativeLevel():設置協作權限,不然沒有聲音。
 19  * IDirectSound8->CreateSoundBuffer():創建一個主緩沖區對象。
 20  * IDirectSoundBuffer->QueryInterface(IID_IDirectSoundBuffer8..):
 21  *            創建一個副緩沖區對象,用來存儲要播放的聲音數據文件。
 22  * IDirectSoundBuffer8->QueryInterface(IID_IDirectSoundNotify..):
 23  *            創建通知對象,通知應用程序指定播放位置已經達到。
 24  * IDirectSoundNotify8->SetNotificationPositions():設置通知位置。
 25  * IDirectSoundBuffer8->SetCurrentPosition():設置播放的起始點。
 26  * IDirectSoundBuffer8->Play():開始播放。
 27  *
 28  * [循環播放數據]
 29  * IDirectSoundBuffer8->Lock():鎖定副緩沖區,准備寫入數據。
 30  * fread():讀取數據。
 31  * IDirectSoundBuffer8->Unlock():解鎖副緩沖區。
 32  * WaitForMultipleObjects():等待“播放位置已經達到”的通知。
 33  *
 34  * This software plays PCM raw audio data using DirectSound.
 35  * It's the simplest tutorial about DirectSound.
 36  *
 37  * The process is shown as follows:
 38  *
 39  * [Init]
 40  * DirectSoundCreate8(): Init DirectSound object.
 41  * SetCooperativeLevel(): Must set, or we won't hear sound.
 42  * IDirectSound8->CreateSoundBuffer(): Create primary sound buffer.
 43  * IDirectSoundBuffer->QueryInterface(IID_IDirectSoundBuffer8..): 
 44  *            Create secondary sound buffer.
 45  * IDirectSoundBuffer8->QueryInterface(IID_IDirectSoundNotify..): 
 46  *            Create Notification object.
 47  * IDirectSoundNotify8->SetNotificationPositions():
 48  *            Set Notification Positions.
 49  * IDirectSoundBuffer8->SetCurrentPosition(): Set position to start.
 50  * IDirectSoundBuffer8->Play(): Begin to play.
 51  *
 52  * [Loop to play data]
 53  * IDirectSoundBuffer8->Lock(): Lock secondary buffer.
 54  * fread(): get PCM data.
 55  * IDirectSoundBuffer8->Unlock(): UnLock secondary buffer.
 56  * WaitForMultipleObjects(): Wait for Notifications.
 57  */
 58 #include <stdio.h>
 59 #include <stdlib.h>
 60 #include <windows.h>
 61 #include <dsound.h>
 62 
 63 
 64 #define MAX_AUDIO_BUF 4 
 65 #define BUFFERNOTIFYSIZE 192000 
 66 
 67 
 68 int sample_rate=8000;    //PCM sample rate
 69 int channels=1;            //PCM channel number
 70 int bits_per_sample=16;    //bits per sample
 71 
 72 BOOL main(int argc,char * argv[])
 73 {
 74     int i;
 75     FILE * fp;
 76     if((fp=fopen("../out.pcm","rb"))==NULL){
 77         printf("cannot open this file\n");
 78         return -1;
 79     }
 80 
 81     IDirectSound8 *m_pDS=0;                    
 82     IDirectSoundBuffer8 *m_pDSBuffer8=NULL;    //used to manage sound buffers.
 83     IDirectSoundBuffer *m_pDSBuffer=NULL;    
 84     IDirectSoundNotify8 *m_pDSNotify=0;        
 85     DSBPOSITIONNOTIFY m_pDSPosNotify[MAX_AUDIO_BUF];
 86     HANDLE m_event[MAX_AUDIO_BUF];
 87 
 88     SetConsoleTitle(TEXT("Simplest Audio Play DirectSound"));//Console Title
 89     //Init DirectSound
 90     if(FAILED(DirectSoundCreate8(NULL,&m_pDS,NULL)))
 91         return FALSE;
 92     if(FAILED(m_pDS->SetCooperativeLevel(FindWindow(NULL,TEXT("Simplest Audio Play DirectSound")),DSSCL_NORMAL)))
 93         return FALSE;
 94 
 95 
 96     DSBUFFERDESC dsbd;
 97     memset(&dsbd,0,sizeof(dsbd));
 98     dsbd.dwSize=sizeof(dsbd);
 99     dsbd.dwFlags=DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
100     dsbd.dwBufferBytes=MAX_AUDIO_BUF*BUFFERNOTIFYSIZE; 
101     dsbd.lpwfxFormat=(WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
102     dsbd.lpwfxFormat->wFormatTag=WAVE_FORMAT_PCM;   
103     /* format type */
104     (dsbd.lpwfxFormat)->nChannels=channels;          
105     /* number of channels (i.e. mono, stereo...) */
106     (dsbd.lpwfxFormat)->nSamplesPerSec=sample_rate;     
107     /* sample rate */
108     (dsbd.lpwfxFormat)->nAvgBytesPerSec=sample_rate*(bits_per_sample/8)*channels; 
109     /* for buffer estimation */
110     (dsbd.lpwfxFormat)->nBlockAlign=(bits_per_sample/8)*channels;        
111     /* block size of data */
112     (dsbd.lpwfxFormat)->wBitsPerSample=bits_per_sample;     
113     /* number of bits per sample of mono data */
114     (dsbd.lpwfxFormat)->cbSize=0;
115 
116     //Creates a sound buffer object to manage audio samples. 
117     HRESULT hr1;
118     if( FAILED(m_pDS->CreateSoundBuffer(&dsbd,&m_pDSBuffer,NULL))){   
119         return FALSE;
120     }
121     if( FAILED(m_pDSBuffer->QueryInterface(IID_IDirectSoundBuffer8,(LPVOID*)&m_pDSBuffer8))){
122         return FALSE ;
123     }
124     //Get IDirectSoundNotify8
125     if(FAILED(m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID*)&m_pDSNotify))){
126         return FALSE ;
127     }
128     for(i =0;i<MAX_AUDIO_BUF;i++){
129         m_pDSPosNotify[i].dwOffset =i*BUFFERNOTIFYSIZE;
130         m_event[i]=::CreateEvent(NULL,false,false,NULL); 
131         m_pDSPosNotify[i].hEventNotify=m_event[i];
132     }
133     m_pDSNotify->SetNotificationPositions(MAX_AUDIO_BUF,m_pDSPosNotify);
134     m_pDSNotify->Release();
135 
136     //Start Playing
137     BOOL isPlaying =TRUE;
138     LPVOID buf=NULL;
139     DWORD  buf_len=0;
140     DWORD res=WAIT_OBJECT_0;
141     DWORD offset=BUFFERNOTIFYSIZE;
142 
143     m_pDSBuffer8->SetCurrentPosition(0);
144     m_pDSBuffer8->Play(0,0,DSBPLAY_LOOPING);
145     //Loop
146     while(isPlaying){
147         if((res >=WAIT_OBJECT_0)&&(res <=WAIT_OBJECT_0+3)){
148             m_pDSBuffer8->Lock(offset,BUFFERNOTIFYSIZE,&buf,&buf_len,NULL,NULL,0);
149 
150             // 如果是實時音頻播放,那么下面的數據就可以把內存中buf_len大小的數據復制到buf指向的地址即可
151             if(fread(buf,1,buf_len,fp)!=buf_len){
152                 //File End
153                 //Loop:
154                 fseek(fp, 0, SEEK_SET);
155                 fread(buf,1,buf_len,fp);
156                 //Close:
157                 //isPlaying=0;
158             }
159 
160             offset+=buf_len;
161             offset %= (BUFFERNOTIFYSIZE * MAX_AUDIO_BUF);
162             printf("this is %7d of buffer\n",offset);
163             m_pDSBuffer8->Unlock(buf,buf_len,NULL,0);
164         }
165         res = WaitForMultipleObjects (MAX_AUDIO_BUF, m_event, FALSE, INFINITE);
166     }
167 
168     return 0;
169 }

結語

  最后,再次強調下,該博文是整理自http://blog.csdn.net/leixiaohua1020/article/details/40540147。我只是改變了一點點格式,其實改變的地方非常少。只是加了點注釋,即播放實時內存數據怎么使用(這是我在項目中的使用方式)。我一再強調,是為了尊重原博主的工作,畢竟直接把別人的東西拿來當作自己的,那就是小偷了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM