语音聊天实现
引 ? 言 ? ? ?
? 隨著計(jì)算機(jī)網(wǎng)絡(luò)的日益普及,人們通過網(wǎng)絡(luò)進(jìn)行交流顯得越來越重要,于是出現(xiàn)了一系列語(yǔ)音通信的軟件,比如NetMeeting、IPPhone、MediaRing以及VoxPhone等等,但這些軟件都功能完善、相對(duì)獨(dú)立,不利于集成到自己開發(fā)的軟件中,有時(shí)我們也希望將這種語(yǔ)音通信功能集成到自己的軟件中,尤其當(dāng)一個(gè)單位的局域網(wǎng)用戶分散在不同的房間時(shí)。本文給出一種靈活、簡(jiǎn)單的實(shí)現(xiàn)方法,采用基于對(duì)話框的方式編程,硬件上只需要一塊雙DMA通道的聲卡(目前的聲卡大多支持雙DMA通道)和一支耳麥,其余全部由軟件編程實(shí)現(xiàn)。程序在 ? Windows98/2000、Visual ? C++6.0 ? 下編譯通過,在Windows ? NT ? 100M ? 以太網(wǎng)上運(yùn)行良好。 ? ? ?
? 設(shè)計(jì)思路 ? ? ?
? 要實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)語(yǔ)音通信,原理非常簡(jiǎn)單,只要針對(duì)一個(gè)點(diǎn)實(shí)現(xiàn)話音的實(shí)時(shí)采集、處理、播放,同時(shí)能進(jìn)行可靠的傳送和接收,這樣兩點(diǎn)一連便可通話。對(duì)于前者,采用Windows ? MDK的低層音頻服務(wù)比較合適,因?yàn)榈蛯右纛l服務(wù)中的回調(diào)機(jī)制為我們提供了很大的方便。當(dāng)應(yīng)用程序不斷向設(shè)備驅(qū)動(dòng)程序提供音頻數(shù)據(jù)時(shí),設(shè)備驅(qū)動(dòng)程序控制音頻設(shè)備在后臺(tái)完成錄音和放音的具體操作,通過回調(diào)機(jī)制,我們又可以檢測(cè)到什么時(shí)候用完一個(gè)數(shù)據(jù)塊,并及時(shí)傳送下一個(gè)數(shù)據(jù)塊,從而保證了聲音的連續(xù),有了這種單機(jī)上的實(shí)時(shí)采集、回放功能后,接下來的工作就是在網(wǎng)絡(luò)上傳送話音數(shù)據(jù)。在點(diǎn)對(duì)點(diǎn)網(wǎng)絡(luò)傳輸方面,選擇面向連接的TCP協(xié)議,TCP傳輸協(xié)議自動(dòng)處理分組丟失和交付失序問題,這樣我們不用為這些問題操心,只需很好地利用這個(gè)連接,在采集話音回放之前一方面將自己的話音傳給網(wǎng)絡(luò),另一方面接收網(wǎng)絡(luò)傳來的話音,這樣便實(shí)現(xiàn)了點(diǎn)對(duì)點(diǎn)語(yǔ)音通信。其結(jié)構(gòu)框圖如下: ? ? ?
? ? ?
? ? ?
? ? ?
? 具體實(shí)現(xiàn) ? ? ?
? 一、話音的實(shí)時(shí)采集、處理、回放 ? ? ?
? 首先要介紹一下Windows低層波形音頻數(shù)據(jù)塊結(jié)構(gòu) ? WAVEHDR,其聲明如下: ? ? ?
? ? ?
? type ? struct{ ? ?
? LPSTR ? lpData; ? //指向鎖定的數(shù)據(jù)緩沖區(qū)的指針 ? ?
? DWORD ? dwBufferLength; ? //數(shù)據(jù)緩沖區(qū)的大小 ? ?
? DWORD ? dwByteRecorded; ? //錄音時(shí)指明緩沖區(qū)中的數(shù)據(jù)量 ? ?
? DWORD ? dwUser; ? //用戶數(shù)據(jù) ? ?
? DWORD ? dwFlag; ? //提供緩沖區(qū)信息的標(biāo)志 ? ?
? DWORD ? dwLoops; ? //循環(huán)播放的次數(shù) ? ?
? struct ? wavehdr_tag ? * ? lpNext; ? //保留 ? ?
? DWORD ? reserved; ? //保留 ? ?
? } ? WAVEHDR; ? ?
? ? ?
? 聲音的采集和播放都是在操作這個(gè)音頻數(shù)據(jù)塊結(jié)構(gòu),實(shí)際上主要用到的就是第一個(gè)成員變量lpData, ? 所以我們只要在分配緩沖區(qū)(內(nèi)存)的同時(shí)相應(yīng)分配WAVEHDR數(shù)據(jù)塊結(jié)構(gòu),然后將緩沖區(qū)的指針賦給對(duì)應(yīng)的數(shù)據(jù)塊結(jié)構(gòu)的成員變量 ? lpData,這樣當(dāng)一個(gè)緩沖區(qū)填滿后,也就是一個(gè)音頻數(shù)據(jù)塊填滿了,通過消息機(jī)制就可以在消息函數(shù)中進(jìn)行處理和播放,播放完后又可通過消息函數(shù)把緩沖區(qū)再送給音頻設(shè)備輸入驅(qū)動(dòng)程序,繼續(xù)進(jìn)行采集并播放,當(dāng)你一次性分配多個(gè)緩沖區(qū)和數(shù)據(jù)塊結(jié)構(gòu)并賦給音頻設(shè)備輸入驅(qū)動(dòng)程序后,至于把哪個(gè)緩沖區(qū)填滿,然后再把哪個(gè)空緩沖區(qū)賦給設(shè)備輸入驅(qū)動(dòng)程序,不需人為干預(yù),完全由Windows控制,這就是一種用動(dòng)態(tài)循環(huán)緩沖區(qū)實(shí)現(xiàn)話音的實(shí)時(shí)采集、播放的簡(jiǎn)單而巧妙的辦法。實(shí)現(xiàn)步驟: ? ? ?
? ? ?
? 1.初始化操作 ? ? ?
? ? ?
? ①用waveInGetNumDevs()和waveOutGetNumDevs()查看當(dāng)前系統(tǒng)波形音頻輸入、輸出設(shè)備; ? ? ?
? ②按11025Hz,16Bit,單聲道,22K/S的格式設(shè)置WAVEFORMATEX結(jié)構(gòu)的成員變量,也可以改為其他WAVE格式; ? ? ?
? ③用waveInOpen(...) ? 和waveOutOpen(...)分別調(diào)用WAVE_FORMAT_QUERY參數(shù)查看波形輸入設(shè)備是否支持所設(shè)定的格式; ? ? ?
? ④再次用waveInOpen(...) ? 和waveOutOpen(...)分別調(diào)用CALLBACK_WINDOW參數(shù)打開波形輸入設(shè)備; ? ? ?
? ⑤分別給音頻數(shù)據(jù)塊和音頻數(shù)據(jù)緩沖區(qū)分配、鎖定全局內(nèi)存; ? ? ?
? ⑥初始化音頻數(shù)據(jù)塊結(jié)構(gòu)各成員變量,主要是將每個(gè)緩沖區(qū)指針賦給對(duì)應(yīng)數(shù)據(jù)塊結(jié)構(gòu)中的緩沖區(qū)指針變量lpData;調(diào)用waveInPrepareHeader(...)和waveInAddBuffer(...)將音頻數(shù)據(jù)塊賦給輸入設(shè)備驅(qū)動(dòng)程序; ? ? ?
? ⑦調(diào)用waveInStart(...)函數(shù)開始錄音。 ? ?
? 2.消息操作 ? ? ?
? ? ?
? 錄音開始后,每當(dāng)有采樣數(shù)據(jù)填滿數(shù)據(jù)塊后,設(shè)備驅(qū)動(dòng)程序就會(huì)發(fā)消息MM_WIM_DATA給用戶窗口,相應(yīng)的消息回調(diào)函數(shù)OnMmWimData(...)對(duì)數(shù)據(jù)塊中的采樣數(shù)據(jù)進(jìn)行處理,然后就可以發(fā)送給輸出設(shè)備進(jìn)行回放,每當(dāng)一個(gè)音頻數(shù)據(jù)塊播放完畢,設(shè)備驅(qū)動(dòng)程序又會(huì)發(fā)出消息MM_WOM_DONE,相應(yīng)的消息回調(diào)函數(shù) ? OnMmWomDone(...)記錄音頻數(shù)據(jù)并經(jīng)必要準(zhǔn)備后重新發(fā)送給輸入設(shè)備,以準(zhǔn)備接收后續(xù)的采樣數(shù)據(jù)。這樣,最初為輸入設(shè)備準(zhǔn)備的音頻數(shù)據(jù)塊就在消息的控制下,在輸入、輸出設(shè)備間循環(huán)使用,無需人為控制實(shí)現(xiàn)了實(shí)時(shí)采集、處理和播放。 ? ? ?
? ? ?
? 當(dāng)結(jié)束通話時(shí)要關(guān)閉音頻輸入設(shè)備,這時(shí)音頻設(shè)備驅(qū)動(dòng)程序會(huì)發(fā)送MM_WIM_CLOSE消息,可在相應(yīng)的消息函數(shù)OnMmWimClose(..)中清除賦給輸入、輸出設(shè)備的音頻數(shù)據(jù)塊。 ? ? ?
? TD> ? ?
? 二、基于TCP協(xié)議的點(diǎn)對(duì)點(diǎn)話音傳輸 ? ? ?
? ? ?
? 對(duì)于聲音的傳送和接收主要是采用面向連接的TCP協(xié)議,并用Windows ? Socket進(jìn)行網(wǎng)絡(luò)編程實(shí)現(xiàn),但首先要將發(fā)送和接收的函數(shù)接口放在 ? OnMmWimData(...)函數(shù)中,這樣才能做到采集數(shù)據(jù)塊填滿后被發(fā)送,接收的數(shù)據(jù)收到后被播放。 ? Windows ? Socket對(duì)于從事過網(wǎng)絡(luò)編程的人來說應(yīng)該不陌生,因?yàn)槲覀円獙?shí)現(xiàn)點(diǎn)對(duì)點(diǎn)通信,所以得把客戶和服務(wù)器模式融合為一種模式,讓服務(wù)器可以做客戶,客戶也可以做服務(wù)器,從而使雙方都有呼叫對(duì)方和接受對(duì)方呼叫的能力,這只需增加一個(gè)監(jiān)聽Socket就行了。一旦呼叫連接建立成功,便在兩個(gè)點(diǎn)之間建立了一個(gè)數(shù)據(jù)流,即使雙方不講話,每個(gè)點(diǎn)也在不停地收、發(fā)數(shù)據(jù),一方有話音自然就隨著這個(gè)數(shù)據(jù)流傳給了對(duì)方,所以關(guān)鍵的問題就是怎樣讀取話音數(shù)據(jù)流,因?yàn)門CP提供的流式服務(wù)是不保證邊界的,當(dāng)發(fā)送方想一次發(fā)送 ? 4000個(gè)字節(jié)時(shí),調(diào)用語(yǔ)句Send(sBuffer,4000),并不能保證一定能發(fā)送出4000個(gè)字節(jié);同樣,接收方準(zhǔn)備一次接收發(fā)過來的數(shù)據(jù),調(diào)用語(yǔ)句Receive(rBuffer,4000),也不能保證一定能接收4000個(gè)字節(jié),因此實(shí)際一次發(fā)送和接收的字節(jié)數(shù)會(huì)是1到4000中的任何一個(gè)值,最壞的情況是只有1個(gè)字節(jié)。相反,如果用Send函數(shù)連續(xù)發(fā)送少量數(shù)據(jù),比如一次發(fā)送400個(gè)字節(jié),連續(xù)發(fā)送10次,接收方用Receive函數(shù)可能一次就把這4000個(gè)字節(jié)都接收下來了,而為了實(shí)現(xiàn)播放,我們希望調(diào)用一次發(fā)送函數(shù)就能把緩沖區(qū)大小的話音數(shù)據(jù)發(fā)送出去,調(diào)用一次接收函數(shù)就能把對(duì)方一次發(fā)送的話音數(shù)據(jù)準(zhǔn)確接收下來,以便進(jìn)行播放,所以一種比較簡(jiǎn)單實(shí)用的辦法,就是利用TCP協(xié)議發(fā)送數(shù)據(jù)時(shí)為每個(gè)數(shù)據(jù)包加個(gè)標(biāo)志頭: ? ? ?
? ? ?
? ? ?
? ? ?
? ? ?
? 這個(gè)標(biāo)志頭包含長(zhǎng)型(4字節(jié))的話音數(shù)據(jù)量值和一個(gè)標(biāo)志字符串,程序中可為這兩項(xiàng)標(biāo)志相應(yīng)設(shè)置偏移量,同時(shí)也為話音數(shù)據(jù)設(shè)置偏移量,通過重載OnReceive(...)進(jìn)行接收。開始接收前,偏移量都置0,接收開始后先檢測(cè)是否收到了4 ? 個(gè)字節(jié)的話音數(shù)據(jù)量大小值和字符串標(biāo)志,如果沒有收到,則通過偏移量來控制將它們準(zhǔn)確收到,之后校驗(yàn)字符串標(biāo)志的正確性,如果兩項(xiàng)標(biāo)志都正確收到了,則按收到的數(shù)據(jù)量大小值進(jìn)行真正的話音數(shù)據(jù)接收,如果在OnReceive函數(shù)中一次調(diào)用Receive(...)接收沒有達(dá)到這個(gè)值,則采用非阻塞模式,調(diào)用AsyncSelect(...)函數(shù)繼續(xù)接收,直至全部收到,這樣重載 ? OnReceive(...)接收函數(shù)后就可以一次接收對(duì)方發(fā)過來的數(shù)據(jù);同理,我們重載CAsyncSocket ? 的OnSend函數(shù),也可以實(shí)現(xiàn)一次發(fā)完一個(gè)緩沖區(qū)中的數(shù)據(jù)。這樣只要將收發(fā)函數(shù)的接口放在OnMmWimData(...)函數(shù)中,使收和發(fā)都產(chǎn)生恒定速率的數(shù)據(jù)流,從而實(shí)現(xiàn)了網(wǎng)絡(luò)話音的傳輸和回放。 ? ? ?
? ? ?
? 三、界面及其他功能 ? ? ?
? ? ?
? 界面及其他功能也要做一番設(shè)計(jì): ? ? ?
? ? ?
? 1.設(shè)置“查找鄰居"項(xiàng),可查看誰在網(wǎng)上,用ListBox進(jìn)行顯示,在ListBox選中一位鄰居,對(duì)應(yīng)的鄰居編輯框中就顯示出該鄰居,便于呼叫,“查找鄰居"主要用到的函數(shù)是WnetOpenEnum(...)、WnetEnumResource(...)、 ? WnetCloseEnum(...)。 ? ? ?
? ? ?
? 2.程序是基于對(duì)話框的,運(yùn)行后不做任何顯示,直接放入系統(tǒng)托盤,直至雙擊托盤圖標(biāo)或選擇菜單再運(yùn)行,但可隨時(shí)監(jiān)聽是否有呼叫接入,一旦連接建立立刻開啟話音處理功能。 ? ? ?
? ? ?
? ? ?
? ? ?
? ? ?
? 3.每次運(yùn)行都在注冊(cè)表的RunServicesOnce鍵中寫入或修改NetPhone項(xiàng),使程序能保持開機(jī)就自動(dòng)運(yùn)行。 ? ? ?
? ? ?
? 4.加入了調(diào)節(jié)麥克和耳機(jī)音量的功能。 ? ? ?
? ? ?
? 5.為通話對(duì)方提供音樂播放,但只支持與采樣格式相同的.wav文件,其他格式的.wav文件可用Windows的錄音機(jī)轉(zhuǎn)換即可。
? 隨著計(jì)算機(jī)網(wǎng)絡(luò)的日益普及,人們通過網(wǎng)絡(luò)進(jìn)行交流顯得越來越重要,于是出現(xiàn)了一系列語(yǔ)音通信的軟件,比如NetMeeting、IPPhone、MediaRing以及VoxPhone等等,但這些軟件都功能完善、相對(duì)獨(dú)立,不利于集成到自己開發(fā)的軟件中,有時(shí)我們也希望將這種語(yǔ)音通信功能集成到自己的軟件中,尤其當(dāng)一個(gè)單位的局域網(wǎng)用戶分散在不同的房間時(shí)。本文給出一種靈活、簡(jiǎn)單的實(shí)現(xiàn)方法,采用基于對(duì)話框的方式編程,硬件上只需要一塊雙DMA通道的聲卡(目前的聲卡大多支持雙DMA通道)和一支耳麥,其余全部由軟件編程實(shí)現(xiàn)。程序在 ? Windows98/2000、Visual ? C++6.0 ? 下編譯通過,在Windows ? NT ? 100M ? 以太網(wǎng)上運(yùn)行良好。 ? ? ?
? 設(shè)計(jì)思路 ? ? ?
? 要實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)語(yǔ)音通信,原理非常簡(jiǎn)單,只要針對(duì)一個(gè)點(diǎn)實(shí)現(xiàn)話音的實(shí)時(shí)采集、處理、播放,同時(shí)能進(jìn)行可靠的傳送和接收,這樣兩點(diǎn)一連便可通話。對(duì)于前者,采用Windows ? MDK的低層音頻服務(wù)比較合適,因?yàn)榈蛯右纛l服務(wù)中的回調(diào)機(jī)制為我們提供了很大的方便。當(dāng)應(yīng)用程序不斷向設(shè)備驅(qū)動(dòng)程序提供音頻數(shù)據(jù)時(shí),設(shè)備驅(qū)動(dòng)程序控制音頻設(shè)備在后臺(tái)完成錄音和放音的具體操作,通過回調(diào)機(jī)制,我們又可以檢測(cè)到什么時(shí)候用完一個(gè)數(shù)據(jù)塊,并及時(shí)傳送下一個(gè)數(shù)據(jù)塊,從而保證了聲音的連續(xù),有了這種單機(jī)上的實(shí)時(shí)采集、回放功能后,接下來的工作就是在網(wǎng)絡(luò)上傳送話音數(shù)據(jù)。在點(diǎn)對(duì)點(diǎn)網(wǎng)絡(luò)傳輸方面,選擇面向連接的TCP協(xié)議,TCP傳輸協(xié)議自動(dòng)處理分組丟失和交付失序問題,這樣我們不用為這些問題操心,只需很好地利用這個(gè)連接,在采集話音回放之前一方面將自己的話音傳給網(wǎng)絡(luò),另一方面接收網(wǎng)絡(luò)傳來的話音,這樣便實(shí)現(xiàn)了點(diǎn)對(duì)點(diǎn)語(yǔ)音通信。其結(jié)構(gòu)框圖如下: ? ? ?
? ? ?
? ? ?
? ? ?
? 具體實(shí)現(xiàn) ? ? ?
? 一、話音的實(shí)時(shí)采集、處理、回放 ? ? ?
? 首先要介紹一下Windows低層波形音頻數(shù)據(jù)塊結(jié)構(gòu) ? WAVEHDR,其聲明如下: ? ? ?
? ? ?
? type ? struct{ ? ?
? LPSTR ? lpData; ? //指向鎖定的數(shù)據(jù)緩沖區(qū)的指針 ? ?
? DWORD ? dwBufferLength; ? //數(shù)據(jù)緩沖區(qū)的大小 ? ?
? DWORD ? dwByteRecorded; ? //錄音時(shí)指明緩沖區(qū)中的數(shù)據(jù)量 ? ?
? DWORD ? dwUser; ? //用戶數(shù)據(jù) ? ?
? DWORD ? dwFlag; ? //提供緩沖區(qū)信息的標(biāo)志 ? ?
? DWORD ? dwLoops; ? //循環(huán)播放的次數(shù) ? ?
? struct ? wavehdr_tag ? * ? lpNext; ? //保留 ? ?
? DWORD ? reserved; ? //保留 ? ?
? } ? WAVEHDR; ? ?
? ? ?
? 聲音的采集和播放都是在操作這個(gè)音頻數(shù)據(jù)塊結(jié)構(gòu),實(shí)際上主要用到的就是第一個(gè)成員變量lpData, ? 所以我們只要在分配緩沖區(qū)(內(nèi)存)的同時(shí)相應(yīng)分配WAVEHDR數(shù)據(jù)塊結(jié)構(gòu),然后將緩沖區(qū)的指針賦給對(duì)應(yīng)的數(shù)據(jù)塊結(jié)構(gòu)的成員變量 ? lpData,這樣當(dāng)一個(gè)緩沖區(qū)填滿后,也就是一個(gè)音頻數(shù)據(jù)塊填滿了,通過消息機(jī)制就可以在消息函數(shù)中進(jìn)行處理和播放,播放完后又可通過消息函數(shù)把緩沖區(qū)再送給音頻設(shè)備輸入驅(qū)動(dòng)程序,繼續(xù)進(jìn)行采集并播放,當(dāng)你一次性分配多個(gè)緩沖區(qū)和數(shù)據(jù)塊結(jié)構(gòu)并賦給音頻設(shè)備輸入驅(qū)動(dòng)程序后,至于把哪個(gè)緩沖區(qū)填滿,然后再把哪個(gè)空緩沖區(qū)賦給設(shè)備輸入驅(qū)動(dòng)程序,不需人為干預(yù),完全由Windows控制,這就是一種用動(dòng)態(tài)循環(huán)緩沖區(qū)實(shí)現(xiàn)話音的實(shí)時(shí)采集、播放的簡(jiǎn)單而巧妙的辦法。實(shí)現(xiàn)步驟: ? ? ?
? ? ?
? 1.初始化操作 ? ? ?
? ? ?
? ①用waveInGetNumDevs()和waveOutGetNumDevs()查看當(dāng)前系統(tǒng)波形音頻輸入、輸出設(shè)備; ? ? ?
? ②按11025Hz,16Bit,單聲道,22K/S的格式設(shè)置WAVEFORMATEX結(jié)構(gòu)的成員變量,也可以改為其他WAVE格式; ? ? ?
? ③用waveInOpen(...) ? 和waveOutOpen(...)分別調(diào)用WAVE_FORMAT_QUERY參數(shù)查看波形輸入設(shè)備是否支持所設(shè)定的格式; ? ? ?
? ④再次用waveInOpen(...) ? 和waveOutOpen(...)分別調(diào)用CALLBACK_WINDOW參數(shù)打開波形輸入設(shè)備; ? ? ?
? ⑤分別給音頻數(shù)據(jù)塊和音頻數(shù)據(jù)緩沖區(qū)分配、鎖定全局內(nèi)存; ? ? ?
? ⑥初始化音頻數(shù)據(jù)塊結(jié)構(gòu)各成員變量,主要是將每個(gè)緩沖區(qū)指針賦給對(duì)應(yīng)數(shù)據(jù)塊結(jié)構(gòu)中的緩沖區(qū)指針變量lpData;調(diào)用waveInPrepareHeader(...)和waveInAddBuffer(...)將音頻數(shù)據(jù)塊賦給輸入設(shè)備驅(qū)動(dòng)程序; ? ? ?
? ⑦調(diào)用waveInStart(...)函數(shù)開始錄音。 ? ?
? 2.消息操作 ? ? ?
? ? ?
? 錄音開始后,每當(dāng)有采樣數(shù)據(jù)填滿數(shù)據(jù)塊后,設(shè)備驅(qū)動(dòng)程序就會(huì)發(fā)消息MM_WIM_DATA給用戶窗口,相應(yīng)的消息回調(diào)函數(shù)OnMmWimData(...)對(duì)數(shù)據(jù)塊中的采樣數(shù)據(jù)進(jìn)行處理,然后就可以發(fā)送給輸出設(shè)備進(jìn)行回放,每當(dāng)一個(gè)音頻數(shù)據(jù)塊播放完畢,設(shè)備驅(qū)動(dòng)程序又會(huì)發(fā)出消息MM_WOM_DONE,相應(yīng)的消息回調(diào)函數(shù) ? OnMmWomDone(...)記錄音頻數(shù)據(jù)并經(jīng)必要準(zhǔn)備后重新發(fā)送給輸入設(shè)備,以準(zhǔn)備接收后續(xù)的采樣數(shù)據(jù)。這樣,最初為輸入設(shè)備準(zhǔn)備的音頻數(shù)據(jù)塊就在消息的控制下,在輸入、輸出設(shè)備間循環(huán)使用,無需人為控制實(shí)現(xiàn)了實(shí)時(shí)采集、處理和播放。 ? ? ?
? ? ?
? 當(dāng)結(jié)束通話時(shí)要關(guān)閉音頻輸入設(shè)備,這時(shí)音頻設(shè)備驅(qū)動(dòng)程序會(huì)發(fā)送MM_WIM_CLOSE消息,可在相應(yīng)的消息函數(shù)OnMmWimClose(..)中清除賦給輸入、輸出設(shè)備的音頻數(shù)據(jù)塊。 ? ? ?
? TD> ? ?
? 二、基于TCP協(xié)議的點(diǎn)對(duì)點(diǎn)話音傳輸 ? ? ?
? ? ?
? 對(duì)于聲音的傳送和接收主要是采用面向連接的TCP協(xié)議,并用Windows ? Socket進(jìn)行網(wǎng)絡(luò)編程實(shí)現(xiàn),但首先要將發(fā)送和接收的函數(shù)接口放在 ? OnMmWimData(...)函數(shù)中,這樣才能做到采集數(shù)據(jù)塊填滿后被發(fā)送,接收的數(shù)據(jù)收到后被播放。 ? Windows ? Socket對(duì)于從事過網(wǎng)絡(luò)編程的人來說應(yīng)該不陌生,因?yàn)槲覀円獙?shí)現(xiàn)點(diǎn)對(duì)點(diǎn)通信,所以得把客戶和服務(wù)器模式融合為一種模式,讓服務(wù)器可以做客戶,客戶也可以做服務(wù)器,從而使雙方都有呼叫對(duì)方和接受對(duì)方呼叫的能力,這只需增加一個(gè)監(jiān)聽Socket就行了。一旦呼叫連接建立成功,便在兩個(gè)點(diǎn)之間建立了一個(gè)數(shù)據(jù)流,即使雙方不講話,每個(gè)點(diǎn)也在不停地收、發(fā)數(shù)據(jù),一方有話音自然就隨著這個(gè)數(shù)據(jù)流傳給了對(duì)方,所以關(guān)鍵的問題就是怎樣讀取話音數(shù)據(jù)流,因?yàn)門CP提供的流式服務(wù)是不保證邊界的,當(dāng)發(fā)送方想一次發(fā)送 ? 4000個(gè)字節(jié)時(shí),調(diào)用語(yǔ)句Send(sBuffer,4000),并不能保證一定能發(fā)送出4000個(gè)字節(jié);同樣,接收方準(zhǔn)備一次接收發(fā)過來的數(shù)據(jù),調(diào)用語(yǔ)句Receive(rBuffer,4000),也不能保證一定能接收4000個(gè)字節(jié),因此實(shí)際一次發(fā)送和接收的字節(jié)數(shù)會(huì)是1到4000中的任何一個(gè)值,最壞的情況是只有1個(gè)字節(jié)。相反,如果用Send函數(shù)連續(xù)發(fā)送少量數(shù)據(jù),比如一次發(fā)送400個(gè)字節(jié),連續(xù)發(fā)送10次,接收方用Receive函數(shù)可能一次就把這4000個(gè)字節(jié)都接收下來了,而為了實(shí)現(xiàn)播放,我們希望調(diào)用一次發(fā)送函數(shù)就能把緩沖區(qū)大小的話音數(shù)據(jù)發(fā)送出去,調(diào)用一次接收函數(shù)就能把對(duì)方一次發(fā)送的話音數(shù)據(jù)準(zhǔn)確接收下來,以便進(jìn)行播放,所以一種比較簡(jiǎn)單實(shí)用的辦法,就是利用TCP協(xié)議發(fā)送數(shù)據(jù)時(shí)為每個(gè)數(shù)據(jù)包加個(gè)標(biāo)志頭: ? ? ?
? ? ?
? ? ?
? ? ?
? ? ?
? 這個(gè)標(biāo)志頭包含長(zhǎng)型(4字節(jié))的話音數(shù)據(jù)量值和一個(gè)標(biāo)志字符串,程序中可為這兩項(xiàng)標(biāo)志相應(yīng)設(shè)置偏移量,同時(shí)也為話音數(shù)據(jù)設(shè)置偏移量,通過重載OnReceive(...)進(jìn)行接收。開始接收前,偏移量都置0,接收開始后先檢測(cè)是否收到了4 ? 個(gè)字節(jié)的話音數(shù)據(jù)量大小值和字符串標(biāo)志,如果沒有收到,則通過偏移量來控制將它們準(zhǔn)確收到,之后校驗(yàn)字符串標(biāo)志的正確性,如果兩項(xiàng)標(biāo)志都正確收到了,則按收到的數(shù)據(jù)量大小值進(jìn)行真正的話音數(shù)據(jù)接收,如果在OnReceive函數(shù)中一次調(diào)用Receive(...)接收沒有達(dá)到這個(gè)值,則采用非阻塞模式,調(diào)用AsyncSelect(...)函數(shù)繼續(xù)接收,直至全部收到,這樣重載 ? OnReceive(...)接收函數(shù)后就可以一次接收對(duì)方發(fā)過來的數(shù)據(jù);同理,我們重載CAsyncSocket ? 的OnSend函數(shù),也可以實(shí)現(xiàn)一次發(fā)完一個(gè)緩沖區(qū)中的數(shù)據(jù)。這樣只要將收發(fā)函數(shù)的接口放在OnMmWimData(...)函數(shù)中,使收和發(fā)都產(chǎn)生恒定速率的數(shù)據(jù)流,從而實(shí)現(xiàn)了網(wǎng)絡(luò)話音的傳輸和回放。 ? ? ?
? ? ?
? 三、界面及其他功能 ? ? ?
? ? ?
? 界面及其他功能也要做一番設(shè)計(jì): ? ? ?
? ? ?
? 1.設(shè)置“查找鄰居"項(xiàng),可查看誰在網(wǎng)上,用ListBox進(jìn)行顯示,在ListBox選中一位鄰居,對(duì)應(yīng)的鄰居編輯框中就顯示出該鄰居,便于呼叫,“查找鄰居"主要用到的函數(shù)是WnetOpenEnum(...)、WnetEnumResource(...)、 ? WnetCloseEnum(...)。 ? ? ?
? ? ?
? 2.程序是基于對(duì)話框的,運(yùn)行后不做任何顯示,直接放入系統(tǒng)托盤,直至雙擊托盤圖標(biāo)或選擇菜單再運(yùn)行,但可隨時(shí)監(jiān)聽是否有呼叫接入,一旦連接建立立刻開啟話音處理功能。 ? ? ?
? ? ?
? ? ?
? ? ?
? ? ?
? 3.每次運(yùn)行都在注冊(cè)表的RunServicesOnce鍵中寫入或修改NetPhone項(xiàng),使程序能保持開機(jī)就自動(dòng)運(yùn)行。 ? ? ?
? ? ?
? 4.加入了調(diào)節(jié)麥克和耳機(jī)音量的功能。 ? ? ?
? ? ?
? 5.為通話對(duì)方提供音樂播放,但只支持與采樣格式相同的.wav文件,其他格式的.wav文件可用Windows的錄音機(jī)轉(zhuǎn)換即可。
總結(jié)
- 上一篇: 语句摘抄——第12周
- 下一篇: 同是办公软件,wps和office有什么