当前位置:
文档之家› 多线程语音聊天系统的设计与实现
多线程语音聊天系统的设计与实现
1 常见语音录制播放技术
音频采集和播放有几种模式:高级媒体控制接口MCI; 低级音频设备驱动接口Waveform Audio;微软DirectX SDK 的组件DirectSoundCaputure和DirectSound。MCI接口只能采 集整个音频文件,无法对音频流进行处理[1]。DirectSoundCa pture的采集接口比起DirectSound的播放接口要复杂很多[3]。 这 两 个 组 件 在 DirectX SDK8.0 以 上 版 本 可 以 用 DirectPlayVoice来代替, DirectPlayVoice不仅封装了采集播放 接口,而且封装了通信接口和多线程技术,可以用来构建 游 戏语音通信。由于DirectX技术封装得比较严实,因此本 文 直接用低级音频设备驱动接口Waveform Audio API来采集和 播放音频,使用UDP Socket来传送音频数据,并引入Win32多 线程技术,从低层来直观地介绍语音聊天系统构建的技 术
难点。
2 语音聊天系统的架构
2.1 多线程框架 本文介绍的语音聊天程序工作在peer to peer 模式,收发
双方使用同一个程序。图1显示了语音聊天的几个环节。
语音录制
压缩
发送
语音播放
解压
接收
图 图1 1语语音音聊聊天天系系统统结结构构
其中,主线程用来响应程序界面,语音采集和语音播放 分别使用不同的线程,而语音数据的压缩和收发功能集成到 主线程中,以减少线程数目和开销。 2.2 通信机制和工作状态
MM_WIM_OPEN(MM_WOM_OPEN): 打开录音/放音设备的消 息。
MM_WIM_CLOSE(MM_WOM_CLOSE): 关闭录音/放音设备的 消息。
MM_WIM_DATA: 当录音填满一个缓冲区时发出的消息。 MM_WOM_DONE: 当播音缓冲区的数据播放完毕,该缓冲区交 还程序时发出的消息。
BEGIN_MESSAGE_MAP(CIPPhone, CDialog)
ON_MESSAGE(WM_RECV,ON_RECV)
END_MESSAGE_MAP();
函数 CIPPhone::ON_RECV用来响应接收数据的网络事 件,在该函数内部,使用recvfrom(recv,buff,2048,0,&Addr,& tmp)从接收套接字recv中接收通话指令或音频数据,接收信 息存放在缓冲区buff中。
表1 录制和压缩语音格式
WAVEFORMATEX成员 录制格式 压缩格式
wFormatTag;
1
34
nChannels
1
1
nSamplesPerSec
12 000
8 000
nAvgBytesPerSec
24 000
1 067
nBlockAlign
2
32
wBitsPerSample
16
1
cbSize;
谢志鹏,吴清江
(华侨大学计算机科学系,泉州 362011) 摘 要:分析了语音聊天系统研制中的几个技术难点,包括语音录制、压缩、传输和播放,解决了录放音的消息处理、多线程与多缓冲机制 在该系统中的运用以及音频压缩解压问题,使该系统的语音通信效果连贯、流畅。 关键词:多线程;语音录制;音频压缩管理;多缓冲队列
Design and Implementation of Multithread Voice Chat System
XIE Zhipeng, WU Qingjiang
(Computer Science Department,Huaqiao University, Quanzhou 362011) 【Abstract 】 In the language of visual C++ ,this paper analyzes the key points in the design of voice chat system, including the voice recording ,
录音缓冲区队列。 (2) 开始录音,一旦录音数据充满当前录音缓冲区,录
音数据可以继续填充队列中下一个录音缓冲区。 (3) 对填满录音数据的缓冲区解锁,取出该缓冲区的语
音数据,压缩后发送; 将该缓冲区重新加入录音缓冲区队列。 4.2 录音线程中消息的映射
ch='CHAT220.220.220.1'
to.sin_addr.S_un.S_addr=m_RemoteIP
sendto(send,ch,sizeof(ch),0,&to,sizeof(to))
2.5 被叫方在函数ON__RECV中确认通话邀请, 进入语音通 信状态。
被叫方接收通话邀请,进入语音通信状态,启动录音和放 音线程,并向主叫方发出确认消息,主叫方收到确认消息,也进 入语音通信状态,启动录音和放音线程。双方开始均处于侦 听邀请状态, bWaiting均为true, 由于通话双方使用同一个程 序,因此用一个布尔变量bPassive来区分主叫还是被叫,如果是 一方发出通话邀请,则该方成为主叫方,bPassive为true。
处理录音或放音消息有4种方式:包括回调函数,事件,窗 口消息或线程。打开录音设备函数waveInOpen和打开放音设 备函数waveOutOpen中的参数fdwOpen 的取值决定了采用何 种方式来响应消息,取值详见msdn帮助。在多线程的架构 下,回调函数,窗口消息均必须把消息投递到录音或放音线程 处理,因此与线程方式是一样的。如果使用事件方式,当 录 音满或放音完时,相应的事件处于有信号状态,则可以在 录 放音线程函数中用WaitForSingleObject响应该事件,进行后 续处理,这和directSoundCapture的事件处理方式类似。综上 所述,线程方式的处理显然更方便,它可以在线程内部直接 响应录音或放音消息,本系统就采用线程方式。 3.2 录放音的消息定义
3.3 波形格式 语音录制采用的波形格式为微软PCM(脉冲编码调制)无
压缩线性格式即WAVE_FORMAT_PCM,各种波形格式 标 志的定义详见mmreg.h。
—130—
typedef struct { WORD wFormatTag; 标志各种波形格式 WORD nChannels; 声道数 DWORD nSamplesPerSec; 采样频率 DWORD nAvgBytesPerSec; 每秒采样音频字节数 WORD nBlockAlign; 音频数据单元 WORD wBitsPerSample;每声道每样本采样位数 WORD cbSize; 压缩系数,pcm格式忽略
compression ,network transferring and playback ,resolves problems including message handling of recording and playing back, the application of multithread and multi buffer mechanism in this system , and audio compression and decompression. Therefor this chatting system performs uninterruptedly and with good fluency. 【Key words】Multithread; Voice recording; Audio compression manager; Multi buffer queue
主 线 程 在 接受邀请 启动录放音线程
2003 端口侦
2003 端口接收语音
听通话邀请 退出通话 2002 端口发送语音
图2 侦听通话邀请和通话状态的转换
2.3 套接字的通信模式和网络事件 在程序初始化的过程中,要建立并绑定接收套接字recv
和发送套接字send,并指定为异步非阻塞模式。以下代码建 立并绑定了接收套接字recv,使程序处于侦听通话邀请状态。
recvaddr.sin_port=htons(2003);
bind(recv,&recvaddr,sizeof(recvaddr));
WSAAsyncSelect(recv,hWnd,WM_RECV,FD_READ);
WSAAsyncSelect语句指定接收套接字recv工作在异步非 阻塞模式,并将接收数据事件FD_READ映射到窗口自定义 消息WM_RECV上,因此必须对该消息进行响应,代码如下 所示:
第30卷 第23期 Vol.30 № 23
计 算 机 工 程 Computer Engineering
2004年12月 December 2004
·多媒体技术及应用·
文章编号:1000—3428(2004)23 —0129—03
文献标识码:A
中图分类号:TP311.52
多线程语音聊天系统的设计与实现
为实现音频多播和大数据量传送,采用UDP Socket而不 用TCP Socket, 该Socket工作在异步模式下,以减少同步模式 而增加的额外线程;为实现全双工语音通信,分别建立发送 和接收UDP Socket,绑定在发送端口2002和接收端口2003;
收发双方启动程序后,均处于侦听通话邀请状态,当一方向 另一方发出通话邀请后,被叫方接受邀请,则双方进入语音 通信状态,双方均启动录音和放音线程,进行全双工的语音 通信;如果一方退出通话,则向对方发送退出对话指令,双 方均终止录音和放音线程,重新处于侦听通话邀请状态,该 状态转换如图2所示。
sendto(send,ch,sizeof(ch),0,&to,sizeof(to))} //启动录音和放音线程
m_pRecordThread = new CRecordThread();
m_pRecordThread->CreateThread();