無標題文件

Gifts & Crafts

音響-DirectX技術實現視頻會議中的音頻通


音響Blog

視頻會議以其方便、快捷、「面對面」交流的優點逐漸得到了人們的認可,許多企事業單位、教育單位,醫療單位都希望使用視頻會議來代替傳統的會議形式。在視頻會議中,與會者之間主要傳輸的是音頻數據和視頻數據,其中的音頻數據顯得更為重要。因為會議中的大部分有用信息都包含在與會者的言語交流上,所以視頻會議系統必須保證音頻通信的流暢性和全雙工,才能使視頻會議更接近於真實的會議環境。

DirectX是Microsoft開發的專門用於開發遊戲和多媒體軟件的應用程序接口(API),包括了對二維和三維圖像、聲音、音樂和針對網絡多人遊戲的網絡通信的強大支持。DirectX是一種標準的軟件接口,所有主要的硬件供應商都提供支持DirectX的驅動設備,應用DirectX的軟件可以在不同的硬件環境下正常運行。另一方面,DirectX能根據所使用的不同硬件,來選擇適當的方式使用硬件加速能力,便於開發高質量的多媒體和遊戲軟件。在DirectX所提供的眾多組件中,用於音頻處理的是DirectSound組件。為保證視頻會議系統中語音的流暢性,需要採用DirectSound中提供的StreamingBuffer(流式緩衝)機制來實現。而為了保證視頻會議系統中的全雙工音頻通信,主要利用的則是DirectSound中的混音機制來實現。

利用StreamingBuffer實現流暢的語音交流

DirectSound中提供了兩種緩衝機制,分別是StaticBuffer(靜態緩衝)和StreamingBuffer(流式緩衝)。StaticBuffer指一次將一段完整的聲音存入緩衝中;StreamingBuffer指的是並不將全部的數據一次讀入緩衝,而是在播放聲音時動態地讀入,佔用空間較小。一般來說,如果聲音需要反覆播放而且容量有限(如遊戲音效),使用StaticBuffer更有助於提高程序的效率;相反,如果是容量很大、實時性要求較高的音頻數據流,則使用StreamingBuffer為佳。在視頻會議系統中,如使用StaticBuffer,則在向緩衝區寫入新的音頻數據時,聲音的回放必然出現短暫停頓,使與會者的完整話語不能夠連續播放,影響通話的流暢性,而StreamingBuffer可克服語音不連續的缺點。

StreamingBuffer提供了兩個指針:PlayCursor(回放游標)和WriteCursor(寫入游標),它們的值只是相對於緩衝區開頭的偏移量而非絕對的內存地址。其中PlayCursor總是指向下一個被輸出的數據字節,而WriteCursor指向的地址則指明從哪個地方開始可以安全地寫入新的音頻數據而不影響回放。按回放音頻數據的順序來看,WriteCursor總是在PlayCursor之前,並且它們間保持著一定的間距,而這個間距會根據不同的系統狀況而有所不同,實驗表明這個間距大概是100~200字節左右。當開始對緩衝區中的音頻數據進行循環模式回放時,總是在PlayCursor所指的地方開始。回放後PlayCursor和WriteCursor會保持它們的間距等速度前移,並且PlayCursor總是指向下一個被輸出的數據字節。當回放到達緩衝區的結尾處時,PlayCursor將重新指向緩衝區的開頭,如此循環下去。而當程序停止對StreamingBuffer中的音頻數據進行回放時,PlayCursor則不再移動,並停留在下一個被輸出的數據字節處,直到重新回放才會繼續前移。

另外,在PlayCursor和WriteCursor之間的區域被認為是即將要進行回放的數據,所以不能夠對其做更新。在理解了StreamingBuffer的基本工作方式後,接下來詳細闡述如何用VisualC++作具體實現,其中會涉及到一些VisualC++的函數,具體可參考MicrosoftMSDN。

 

在程序中,設置一個大小為一幀音頻數據的大小(一般相當於0.25秒的語音)的2倍的StreamingBuffer。並且在StreamingBuffer的正中間和結尾處分別設置標誌一個觸發事件。程序開始時,通過調用Play函數對StreamingBuffer中的數據進行循環回放。當PlayCursor到達正中間和結尾時,事件就會產生,就可以通過程序向緩衝區寫入新一幀的音頻數據。在寫入新一幀音頻數據的過程中,首先調用Lock函數鎖定緩衝區中的部分,此時的WriteCursor被鎖定不再前移,而PlayCursor將跟隨著聲音的回放繼續前進;利用回放PlayCursor和WriteCursor間的音頻數據的一段時間內,根據鎖定時獲得的lplpvAudioPtr1(此時的lplpvAudioPtr1指向的地方就是鎖定時WriteCursor的所指的地方),lpdwAudioBytes1(可安全寫入的音頻數據大小)等與StreamingBuffer相關的參數lplpvAudioPtr2、lpdwAudioBytes2等信息,把數據在指定的地方寫入緩衝區,然後調用Unlock函數解除對WriteCursor的鎖定。這樣,WriteCursor重新調整回與PlayCursor保持100~200字節間距的地方,繼續對新的音頻數據進行回放。上述這個過程在整個程序的運行過程中,不斷地循環進行,如圖1所示,實現了在對StreamingBuffer中舊一幀音頻數據進行回放的同時寫入新一幀的音頻數據。

從理論上講,這已經保證了音頻回放的流暢性。但在實現過程中,由於操作的對象是一幀的音頻數據,其回放的時間僅是0.25秒,所以必須考慮的一個問題是程序的反應速度問題。如果忽略由事件觸發到真正用Lock函數鎖定緩衝區的部分以進行新數據寫入之間的時間,則這種實現方法沒有任何問題。除了最開始的兩幀數據外,新的一幀數據會緊跟在前一幀數據之後,彼此之間沒有重疊部分,也沒有空隙存在,能很好地達到音頻回放的流暢效果。但事實上,由事件觸發到真正用Lock函數鎖定緩衝區的部分以進行新數據的寫入之間還必須經過線程監聽到事件,分析事件對應的緩衝區,然後再觸發相應的回調函數來進行

上面所述的新一幀音頻數據的寫入過程。而這一系列分析工作所佔用的時間是會隨系統當時的狀況而變化的,是一個隨機的時間長度,所以每次對緩衝區用Lock函數鎖定緩衝區的部分時,WriteCursor所在的位置都會不同,這樣就造成新的一幀數據並不一定會嚴格地緊跟在前一幀數據之後,它們之間可能會出現重疊部分,也可能會有空隙出現,不利於音頻數據的連續播放。如果出現重疊部分,那麼回放造成有部分的音頻數據丟失;如果有空隙的出現,會造成語音的不連續或混亂。但經過調試,仔細分析了由事件觸發到真正用Lock函數鎖定緩衝區的部分以進行新數據寫入之間的時間後,發現它對鎖定時WriteCursor所在位置的偏差產生的波動不大,一般由此產生的重疊部分或空隙部分都在50字節左右,也就是說平均每幀數據中會有50字節的錯誤。在程序中,指定的一幀音頻數據為2000字節(與0.25秒相對應),所以會有大概2.5%的音頻數據會出錯。如果以所採用的音頻格式來計算,8KSPS(採樣率)*8Bit(每個採樣用8位表示)=64KBit/s=8KB/s,那麼這2.5%的錯誤在每秒鐘內對應的會是0.025s的音頻數據,基本上人的聽力是難以分辨的。所以在採用StreamingBuffer依然能很好地達到了音頻的流暢性要求。

 

arrow
arrow
    全站熱搜

    seozoap 發表在 痞客邦 留言(0) 人氣()