C++ WinAPI实现局域网语音通话:Winmm音频控制与Socket通信详解

码头散步
码头散步
发布时间:2026-10-15| 3人浏览过
原创

在Windows平台下,利用C++和WinAPI实现局域网语音通话主要涉及三个关键环节:通过winmm库调用Wave In/Out API进行声卡数据交互、构建TCP或UDP Socket进行网络通信,以及设计包含信令(注册、呼叫)和数据的有效载荷协议。本文将拆解底层音频捕获与播放的操作步骤,并阐述如何将这些原始PCM数据通过网络发送给对端。

结果与效果演示

完成上述开发后,程序能够识别系统默认录音设备与播放设备,并在同一局域网内建立连接。用户可听到实时传输的语音内容,系统同时提供来电提醒及在线状态广播。

在实际操作中,当一端开始说话时,声音会被数字化并通过网络传输至另一端,由另一端的播放设备还原为模拟信号输出。

准备条件与环境

  • 操作系统:Microsoft Windows(支持Win32 API环境)
  • 编译器:Visual Studio 或其他支持C++11/14标准的工具链
  • 依赖库:wavemmg.lib(用于音频操作),ws2_32.lib(用于Socket网络操作)
  • 硬件:具备麦克风和扬声器的计算机

真实操作顺序

第1步:枚举音频输入与输出设备

目的:获取系统中可用的录音设备和播放设备列表,供用户选择或默认加载。

入口:使用waveInGetNumDevs和waveOutGetNumDevs函数获取设备总数。

动作:遍历设备ID,调用waveInGetDevCaps或waveOutGetCaps填充结构体以获取设备名称等详细信息。

// 音频输入设备信息的获取
tstring sDevText = L"";		//这里用的是unicode的宽字符串的tstring对象
WAVEINCAPS waveCaps;	//用于获取设备信息的结构体
int res = waveInGetDevCaps(dwID, &waveCaps, sizeof(WAVEINCAPS));
if (res == MMSYSERR_NOERROR)
{
	sDevText = waveCaps.szPname;	//此处保存的是设备名称
}

// 音频输出设备信息的获取,方式和上面类似,只不过函数名称略有不同
tstring sDevText = L"";
WAVEOUTCAPS waveCaps;
int res = waveOutGetDevCaps(dwID, &waveCaps, sizeof(WAVEOUTCAPS));
if (res == MMSYSERR_NOERROR)
{
	sDevText = waveCaps.szPname;
}

第2步:初始化音频设备上下文

目的:打开指定设备进行后续的数据流读写,配置采样格式。

入口:waveInOpen 和 waveOutOpen。

重要参数:

  • m_hWaveIn / m_hWaveOut:指向设备句柄的指针。
  • iWaveDevID:设备ID,若为WAVE_MAPPER则自动选择默认设备。
  • m_soundFormat:PCM格式配置示例:
    • wFormatTag = WAVE_FORMAT_PCM
    • nChannels = 1(单声道)或 2(立体声)
    • nSamplesPerSec = 11025 或 22050/44100(常见采样率)
    • nAvgBytesPerSec:通常等于 nSamplesPerSec * nChannels * wBitsPerSample / 8,文中示例设为与采样率相近值。
    • wBitsPerSample = 8 或 16
  • CALLBACK_WINDOW:指定通过窗口消息接收音频完成通知。

动作:调用打开函数并检查返回值是否为 MMSYSERR_NOERROR。

HWAVEIN m_hWaveIn;							//音频输入的句柄
//打开录音设备,采用窗口方式接收音频消息
int res = waveInOpen(&m_hWaveIn, iWaveDevID, &m_soundFormat, (DWORD)hWnd, 0L, CALLBACK_WINDOW);
if (res != MMSYSERR_NOERROR)
	return false;

//======================== 播放 ==========================
//HWAVEOUT m_hWaveOut; //注意类型区别
res = waveOutOpen(&m_hWaveOut, iWaveDevID, &m_soundFormat, (DWORD)hWnd,
	0L, CALLBACK_WINDOW);
if (res != MMSYSERR_NOERROR)
	return false;

第3步:配置与管理缓冲区

目的:分配内存块供驱动写入或读取音频数据,防止缓冲区溢出或欠载。

参数设置:定义 MAX_BUFFER_SIZE(如 10240 或 20480),平衡延迟与连续性。

动作:

  1. 填充 WAVEHDR 结构体:lpData 指向缓冲内存,dwBufferLength 设为大小,dwFlags 清零。
  2. 调用 waveInPrepareHeader 或 waveOutPrepareHeader 准备缓冲区。
  3. 调用 waveInAddBuffer 将缓冲区加入排队队列;对于输出端,通常配合 waveOutWrite 发送数据。
char m_cBufferIn[MAX_BUFFER_SIZE];	//实际的缓冲区空间
WAVEHDR m_pWaveHdrIn;				//缓冲区头结构体

//准备内存块录音
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;
//增加内存块到输入队列
res = waveInAddBuffer(m_hWaveIn, &m_pWaveHdrIn, sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR)
	return false;

//输出端类似逻辑
res = waveOutWrite(m_hWaveOut, &m_pWaveHdrout, sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR)
	return false;

第4步:启动/停止捕获与播放

目的:开始采集麦克风数据或将缓冲区数据发送至扬声器。

操作指令:

  • 开始录音:waveInStart(m_hWaveIn)
  • 停止录音:waveInStop(m_hWaveIn)
  • 停止播放/重置:waveOutReset(m_hWaveOut)

数据传输:接收到网络语音包后,需将数据拷贝至输出缓冲区(memcpy),然后触发播放。

第5步:处理音频完成消息与缓冲区回收

目的:当缓冲区填满(输入)或播放完毕(输出)时,系统会向指定窗口发送消息。应用必须及时处理并重新提交空闲缓冲区。

动作:

  1. 监听 MM_WIM_DATA 消息(针对输入)。
  2. 从 WAVEHDR::dwBytesRecorded 中获取有效数据长度。
  3. 对数据进行处理(如封装网络包)。
  4. 关键:必须调用 waveInUnprepareHeader 释放原准备状态,修改缓冲区内容后,再次调用 waveInPrepareHeader 和 waveInAddBuffer 重新入队。Windows 不会自动清理缓冲区内容,也不会在处理后自动重新排队。
// 清理并重新准备输入缓冲区
int res = waveInUnprepareHeader(m_hWaveIn, &m_pWaveHdrIn, sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR) return false;

// 重新配置
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;
res = waveInAddBuffer(m_hWaveIn, &m_pWaveHdrIn, sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR) return false;

客户端与服务器通信逻辑设计

音频数据仅是 Payload 的一部分,完整的语音通话还需要可靠的信令控制。建议采用自定义协议包或基于 JSON/XML 的结构化数据包。

信令与数据结构设计

  1. 注册与发现:客户端上线时,服务器分配唯一 ID,并向其他已在线客户端广播新用户的存在信息(包含 ID 和 IP)。
  2. 呼叫请求:主叫方发送 Call Request 包给被叫方,包含对方 ID 和本方 ID。被叫方回复 Ack/Nack。
  3. 语音数据传输:每个音频小包应包含:
    - SenderID:发送者标识
    - ReceiverID:接收者标识(可选,若基于流式全双工可能不需要特定目标,但用于区分多路复用)
    - Timestamp:时间戳,用于抖动缓冲或同步
    - AudioChunk:PCM 二进制数据段
  4. 挂断信号:Call End 包,用于优雅断开连接。需考虑异常掉线时的超时清理机制。

异常排错参考

  • 无声问题:检查 waveInOpen 是否成功;确认缓冲区是否正确入队;确认收到 MM_WIM_DATA 消息后是否有实际的数据处理逻辑。
  • 卡顿或断续:减小 MAX_BUFFER_SIZE 以降低延迟,但过小会导致 CPU 负载过高或频繁中断;增大缓冲区可能导致高延迟。建议根据测试结果微调。
  • 关闭崩溃:程序退出前务必调用 waveInClose 和 waveOutClose,并确保所有未完成的缓冲区已正确清理(Unprepare),否则可能导致驱动层死锁或内存泄漏。
// 安全关闭资源
if (m_hWaveIn) {
    waveInClose(m_hWaveIn);
    m_hWaveIn = NULL;
}
if (m_hWaveOut) {
    waveOutClose(m_hWaveOut);
    m_hWaveOut = NULL;
}

简要总结

通过 C++ 调用 WinAPI 的 Wave 系列函数,结合 Socket 网络编程,可以实现稳定的局域网语音通话。核心在于音频缓冲区的准确管理与循环使用,以及清晰的网络信令状态机维护。以上就是基于C++ WinAPI和Socket实现局域网语音通话的详细内容,更多关于C++多媒体开发及网络编程的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。