為了同時收發不阻塞,建立兩個 UDP Socket:
P1,sendto 發送編碼後語音封包至對方 IP:P2。P2,recvfrom 循環接收封包,解碼播放。關鍵:兩 Socket 必須綁定不同埠號,且需設定 SO_REUSEADDR 允許地址重用 (若同一程式多實例或快速重啟)。
// 1. 初始化
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2), &wsaData);
// 2. 建立發送 Socket
SOCKET sSend = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
BOOL bReuse = TRUE;
setsockopt(sSend, SOL_SOCKET, SO_REUSEADDR, (char*)&bReuse, sizeof(bReuse));
sockaddr_in localSend = {0};
localSend.sin_family = AF_INET;
localSend.sin_port = htons(LOCAL_SEND_PORT);
localSend.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sSend, (sockaddr*)&localSend, sizeof(localSend));
// 3. 建立接收 Socket
SOCKET sRecv = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
setsockopt(sRecv, SOL_SOCKET, SO_REUSEADDR, (char*)&bReuse, sizeof(bReuse));
sockaddr_in localRecv = {0};
localRecv.sin_family = AF_INET;
localRecv.sin_port = htons(LOCAL_RECV_PORT);
localRecv.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sRecv, (sockaddr*)&localRecv, sizeof(localRecv));
// 4. 設定接收緩衝區 (建議 8KB~64KB)
int bufSize = 32768;
setsockopt(sRecv, SOL_SOCKET, SO_RCVBUF, (char*)&bufSize, sizeof(bufSize));
// 5. 目標地址
sockaddr_in remote = {0};
remote.sin_family = AF_INET;
remote.sin_port = htons(REMOTE_PORT);
remote.sin_addr.s_addr = inet_addr("192.168.1.100");
// 6. 發送執行緒
while (running) {
// 從音訊捕獲裝置取得 PCM 資料 → 編碼 (G.711/G.729/Speex/Opus) → 封裝 RTP
sendto(sSend, encodedData, encodedLen, 0, (sockaddr*)&remote, sizeof(remote));
Sleep(20); // 20ms 幀間隔
}
// 7. 接收執行緒
sockaddr_in from;
int fromLen = sizeof(from);
while (running) {
int n = recvfrom(sRecv, buffer, sizeof(buffer), 0, (sockaddr*)&from, &fromLen);
if (n > 0) {
// 驗證來源、解析 RTP、解碼 → 送入播放緩衝隊列
}
}
// 8. 清理
closesocket(sSend);
closesocket(sRecv);
WSACleanup();
waveInOpen / WASAPI / PortAudio 取得 8k/16k Hz 單聲道 PCM。waveOutOpen / WASAPI 低延遲播放。UDP + 雙 Socket + 應用層可靠性機制 (RTP/RTCP、編碼、抖動緩衝) 是語音全雙工通訊的標準架構。Winsock 提供底層傳輸,核心難點在音訊管線與網路抖動對抗,而非 Socket API 本身。