C++用winapi?socket實(shí)現(xiàn)局域網(wǎng)語音通話功能
前幾天看書了解了語音通話的原理,就很想自己嘗試一下,然后就——做出來了,嘻嘻,先看看效果吧!
因?yàn)檫@邊沒辦法上傳視頻,所以只能錄制一個gif大家看一下效果,但是是可以聽到聲音的。
源碼下載鏈接:http://xiazai.jb51.net/202206/yuanma/luyinj_jb51.rar
功能介紹: 1.支持錄音設(shè)備查找以及播放設(shè)備查找 2.支持局域網(wǎng)語音通話 3.通話包含語音來電提醒和掛斷電話的提示信息,還能實(shí)時的獲取在線用戶的數(shù)量以及對應(yīng)的id,其他功能正在開發(fā),期待大家一起進(jìn)步。
一、socket通信
唔,感覺全部放在這里面感覺會很長,以后寫一篇其他的文章詳細(xì)介紹這個內(nèi)容吧。
二、waveIn和WaveOut的Win32API
1.音頻設(shè)備的的信息獲取
首先是輸入音頻設(shè)備個數(shù)的獲取,僅僅通過調(diào)用下面的函數(shù)就可以了,沒有輸入?yún)?shù),輸出設(shè)備個數(shù)獲取的函數(shù)調(diào)用方式一樣,名稱略有不同。 waveInGetNumDevs(); //獲取輸入音頻設(shè)備個數(shù) waveOutGetNumDevs(); //獲取輸出音頻設(shè)備個數(shù) 然后便是獲取具體的設(shè)備信息了,具體調(diào)用如下面所示:
//音頻輸入設(shè)備信息的獲取 tstring sDevText = L""; //這里用的是unicode的寬字符串的tstring對象 WAVEINCAPS waveCaps; //用于獲取設(shè)備信息的結(jié)構(gòu)體 int res = waveInGetDevCaps(dwID, &waveCaps, sizeof(WAVEINCAPS)); if (res == MMSYSERR_NOERROR) { sDevText = waveCaps.szPname; //此處保存的是設(shè)備名稱 } //音頻輸出設(shè)備信息的獲取,方式和上面類似,只不過函數(shù)名稱略有不同 tstring sDevText = L""; WAVEOUTCAPS waveCaps; int res = waveOutGetDevCaps(dwID, &waveCaps, sizeof(WAVEOUTCAPS)); if (res == MMSYSERR_NOERROR) { sDevText = waveCaps.szPname; }
2.音頻設(shè)備的初始化
首先是打開音頻設(shè)備: waveInOpen函數(shù)參數(shù)說明: m_hWaveIn 表示的是音頻輸入的設(shè)備句柄,參數(shù)為該句柄的地址 iWaveInDevID 表示的是音頻輸入設(shè)備的ID,ID默認(rèn)是從0開始的,如果是在不知道音頻ID的話,可以將該參數(shù)設(shè)置為 WAVE_MAPPER ,即默認(rèn)選擇。 m_soundFormat 表示的是打開設(shè)備的格式,這個就略微復(fù)雜一點(diǎn)了,常用的參數(shù)有幾個吧: m_soundFormat.wFormatTag = WAVE_FORMAT_PCM; //這個是采樣數(shù)據(jù)的格式,其他的咱也不懂,就用默認(rèn)的 PCM 脈沖采樣的格式。 m_soundFormat.nChannels = 1; //通道數(shù) m_soundFormat.nSamplesPerSec = 11025; //采樣率,常用的有11.025 kHz、22.05 kHz和44.1 kHz,其他的不建議設(shè)一些不規(guī)則的數(shù)。 m_soundFormat.nAvgBytesPerSec = 11025; //不懂,和采樣率一般設(shè)置為一樣的數(shù) m_soundFormat.wBitsPerSample = 8; //表示的是數(shù)據(jù)位數(shù),8或者16位 m_soundFormat.cbSize = 0; //一般為0 hWnd 這個是用于接收錄音通知消息的句柄,填做主窗口句柄就行,注意一個類型轉(zhuǎn)換。 0L 它的名字是 dwInstance,不太懂,沒什么關(guān)系其實(shí) CALLBACK_WINDOW 表示的是 dwCallback(也就是hWnd) 是個窗口句柄,指定的是 dwCallback(也就是hWnd) 參數(shù)是什么東西。給大家看一下原版的英文解釋,這個有好多種內(nèi)容,不想看的可以跳過了,看起來挺晦澀的:
fdwOpen: Flags for opening the device. The following values are defined: CALLBACK_EVENT The dwCallback parameter is an event handle. CALLBACK_FUNCTION The dwCallback parameter is a callback procedure address. CALLBACK_NULL No callback mechanism. This is the default setting. CALLBACK_THREAD The dwCallback parameter is a thread identifier. CALLBACK_WINDOW The dwCallback parameter is a window handle. WAVE_FORMAT_DIRECT If this flag is specified, the ACM driver does not perform conversions on the audio data. WAVE_FORMAT_QUERY The function queries the device to determine whether it supports the given format, but it does not open the device. WAVE_MAPPED The uDeviceID parameter specifies a waveform-audio device to be mapped to by the wave mapper.
返回值為 MMSYSERR_NOERROR 表示失敗了,然后這里對返回值做一個判斷。
HWAVEIN m_hWaveIn; //音頻輸入的句柄 //打開錄音設(shè)備,采用窗口方式接收音頻消息 int res = waveInOpen(&m_hWaveIn, iWaveInDevID, &m_soundFormat, (DWORD)hWnd, 0L, CALLBACK_WINDOW); if (res != MMSYSERR_NOERROR) return false;
輸出設(shè)備的打開啊方式類似,此處也不做過多解釋了,大家看一下就差不多能懂了,相信能認(rèn)認(rèn)真真看這篇博客的應(yīng)該都是很棒的人。 (注意:此處的 m_hWaveOut 類型是 HWAVEOUT,和上面的那個不一樣,注意區(qū)分哦)
//======================== 播放 ========================== res = waveOutOpen(&m_hWaveOut, iWaveOutDevID, &m_soundFormat, (DWORD)hWnd, 0L, CALLBACK_WINDOW); if (res != MMSYSERR_NOERROR) return false;
3.輸入輸出設(shè)備緩沖區(qū)的準(zhǔn)備和添加
老樣子了,先從音頻輸入設(shè)備講起: MAX_BUFFER_SIZE 是自己設(shè)置的一個宏定義,給大家一個大概的數(shù)量大小參考吧,10240 或者 20480 都可以的,這個其實(shí)是一個平衡,如果緩沖區(qū)過大,那么通話延遲比較高,如果比較少,則通話的連續(xù)性質(zhì)量不高,自己看著試試就行 m_pWaveHdrIn.dwBytesRecorded 表示的是在準(zhǔn)備這個緩沖區(qū)的時候,里面的初始數(shù)據(jù)占多少字節(jié),填個 0 就行。 m_pWaveHdrIn.dwFlags 參數(shù)有好多內(nèi)容,有興趣的可以看看下面的參考內(nèi)容:
方法
提供緩沖區(qū)信息的標(biāo)志。定義了以下值: WHDR_BEGINLOOP 這個緩沖區(qū)是循環(huán)中的第一個緩沖區(qū)。該標(biāo)志僅用于輸出緩沖區(qū)。 WHDR_DONE 由設(shè)備驅(qū)動程序設(shè)置,表示緩沖區(qū)已用完,正在將其返回給應(yīng)用程序。 WHDR_ENDLOOP 這個緩沖區(qū)是循環(huán)中的最后一個緩沖區(qū)。該標(biāo)志僅用于輸出緩沖區(qū)。 WHDR_INQUEUE 由窗口設(shè)置,表示緩沖區(qū)已排隊等待回放。 WHDR_PREPARED 由窗口設(shè)置,表示緩沖區(qū)已用波形預(yù)預(yù)熱器或波形輸出預(yù)預(yù)熱器功能準(zhǔn)備好。
waveInPrepareHeader 函數(shù)主要是準(zhǔn)備音頻輸入設(shè)備的緩沖區(qū)(其實(shí)翻譯一下看名字大概就能猜出來),大概參數(shù)介紹: m_hWaveIn 音頻輸入設(shè)備的句柄 m_pWaveHdrIn 緩沖區(qū)的地址 sizeof(WAVEHDR) 這個參數(shù)么,不用多說了哈哈
下一個函數(shù) waveInAddBuffer 也簡單,不多說了,相信大家的實(shí)力。
char m_cBufferIn[MAX_BUFFER_SIZE]; //這個是實(shí)際的緩沖區(qū)空間 WAVEHDR m_pWaveHdrIn; //這是一個結(jié)構(gòu)體,用于函數(shù)調(diào)用參數(shù)的一個內(nèi)容 //準(zhǔn)備內(nèi)存塊錄音 m_pWaveHdrIn.lpData = m_cBufferIn; m_pWaveHdrIn.dwBufferLength = MAX_BUFFER_SIZE; m_pWaveHdrIn.dwBytesRecorded = 0; m_pWaveHdrIn.dwFlags = 0; res = waveInPrepareHeader(m_hWaveIn, &m_pWaveHdrIn, sizeof(WAVEHDR)); if (res != MMSYSERR_NOERROR) return false; //增加內(nèi)存塊 res = waveInAddBuffer(m_hWaveIn, &m_pWaveHdrIn, sizeof(WAVEHDR)); if (res != MMSYSERR_NOERROR) return false;
音頻輸出設(shè)備的緩沖區(qū)準(zhǔn)備和添加類似,參考下面代碼:
//準(zhǔn)備內(nèi)存塊播放 m_pWaveHdrout.lpData = m_cBufferout; m_pWaveHdrout.dwBufferLength = MAX_BUFFER_SIZE; m_pWaveHdrout.dwBytesRecorded = 0; m_pWaveHdrout.dwFlags = 0; res = waveOutPrepareHeader(m_hWaveOut, &m_pWaveHdrout, sizeof(WAVEHDR)); if (res != MMSYSERR_NOERROR) return false; //指定數(shù)據(jù)塊到音頻播放緩沖區(qū) res = waveOutWrite(m_hWaveOut, &m_pWaveHdrout, sizeof(WAVEHDR)); if (res != MMSYSERR_NOERROR) return false;
4.播放和錄音的開始和終止
先寫這幾個比較簡單的操作: 開始錄音:waveInStart(m_hWaveIn); 停止錄音:waveInStop(m_hWaveIn); 停止播放:waveOutReset(m_hWaveOut); 然后播放錄音的操作略微復(fù)雜一點(diǎn),需要把數(shù)據(jù)放到播放緩沖區(qū),緩沖區(qū)的內(nèi)容會自動播放: m_cBufferout 播放緩沖區(qū)的首地址 pData 要播放的聲音數(shù)據(jù)流首地址 dwDataLen 聲音數(shù)據(jù)流的長度
memcpy(m_cBufferout, pData, dwDataLen); (函數(shù)不唯一啊,這個windows有好多,例如CopyMemory也可以實(shí)現(xiàn)這個功能,這里用的是 memcpy 函數(shù))
5.錄音通知消息的獲取和處理
開始錄音后,緩沖區(qū)不斷地增加捕獲到的音頻數(shù)據(jù),當(dāng)音頻數(shù)據(jù)接受滿了之后,就會向前文說的那個窗口句柄的窗口發(fā)送通知消息 MM_WIM_DATA ,收到這個消息之后程序就要對這些數(shù)據(jù)進(jìn)行處理,處理完畢后最最重要一件事是清空緩沖區(qū),windows并不會自己清理緩沖區(qū)內(nèi)容。 清理緩沖區(qū)用到的函數(shù)是 waveInUnprepareHeader 這個參數(shù)其實(shí)差不多,
waveInPrepareHeader函數(shù)清理由WaveInPrepareHeader函數(shù)執(zhí)行的準(zhǔn)備。該函數(shù)必須在設(shè)備驅(qū)動程序填充緩沖區(qū)并將其返回給應(yīng)用程序后調(diào)用。在釋放緩沖區(qū)之前,您必須調(diào)用此函數(shù)。
這個是官方的解釋,感覺這個挺詳細(xì)的,放在這里大家看看。清空緩沖區(qū)之后,重新準(zhǔn)備緩沖區(qū),和上面的操作一樣。
int res = waveInUnprepareHeader(m_hWaveIn, &m_pWaveHdrIn, sizeof(WAVEHDR)); if (res != MMSYSERR_NOERROR) return false; //準(zhǔn)備內(nèi)存塊錄音 m_pWaveHdrIn.lpData = m_cBufferIn; m_pWaveHdrIn.dwBufferLength = MAX_BUFFER_SIZE; m_pWaveHdrIn.dwFlags = 0; res = waveInPrepareHeader(m_hWaveIn, &m_pWaveHdrIn, sizeof(WAVEHDR)); if (res != MMSYSERR_NOERROR) return false; //增加內(nèi)存塊 res = waveInAddBuffer(m_hWaveIn, &m_pWaveHdrIn, sizeof(WAVEHDR)); if (res != MMSYSERR_NOERROR) return false;
清空輸出緩沖區(qū)的函數(shù)(程序結(jié)束,記得清空緩沖區(qū)內(nèi)容):
int res = waveOutUnprepareHeader(m_hWaveOut, &m_pWaveHdrout[0], sizeof(WAVEHDR));
6.關(guān)閉音頻輸入和輸出設(shè)備
調(diào)用兩個特別簡單的函數(shù)實(shí)現(xiàn)最終的收尾工作,哦耶!
if (m_hWaveIn) { waveInClose(m_hWaveIn); m_hWaveIn = NULL; } if (m_hWaveOut) { waveOutClose(m_hWaveOut); m_hWaveOut = NULL; }
三、通信數(shù)據(jù)包的設(shè)計以及客戶端服務(wù)器邏輯
這個怎么說呢,感覺就要從實(shí)際出發(fā)了,這里簡單的說一下思路吧。 功能分析: 1.客戶端登陸ID分配以及其他客戶端的廣播 可以用靜態(tài)變量++來為客戶端賦值ID,以此保證每個用戶ID不重復(fù),然后廣播就遍歷所有的客戶端。包括登陸包,反饋包,廣播包。 2.撥打電話提示 這個就是撥打電話請求包和撥打電話的回復(fù)包兩個是吧。 3.聲音數(shù)據(jù)的傳輸 必須指定誰的語音信息發(fā)到哪個客戶端,所以語音包必須包含發(fā)送用戶的ID和接收用戶的ID。 4.掛斷通知 需要掛斷包對吧。用戶掛斷情況可能是主動掛斷,或者是程序異常關(guān)閉,所以掛斷包可以添加一點(diǎn)掛斷信息等等。
相關(guān)文章
C語言實(shí)現(xiàn)BMP圖像處理(哈夫曼編碼)
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)BMP圖像哈夫曼編碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10C++基于消息隊列的多線程實(shí)現(xiàn)示例代碼
這篇文章主要給大家介紹了關(guān)于C++基于消息隊列的多線程實(shí)現(xiàn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用C++具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04使用Visual Studio進(jìn)行動態(tài)鏈接庫開發(fā)流程
這篇文章主要介紹了使用Visual Studio進(jìn)行動態(tài)鏈接庫開發(fā)流程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-05-05VS2022調(diào)試通過??禂z像頭煙火識別SDK的實(shí)現(xiàn)
本文主要介紹了VS2022調(diào)試通過??禂z像頭煙火識別SDK的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02