前几天看书了解了语音通话的原理,就很想自己尝试一下,然后就——做出来了,嘻嘻,先看看效果吧!

因为这边没办法上传视频,所以只能录制一个gif大家看一下效果,但是是可以听到声音的。

源码的话我已经上传到csdn了,这个是下载链接:https://download.csdn.net/download/weixin_47232366/19361119

功能介绍:
1.支持录音设备查找以及播放设备查找
2.支持局域网语音通话
3.通话包含语音来电提醒和挂断电话的提示信息,还能实时的获取在线用户的数量以及对应的id,其他功能正在开发,期待大家一起进步。

Summary:
1.socket通信
2.wavein和waveout系列函数的使用
3.各种通信场景的添加以及数据包的设计

一、socket通信

唔,感觉全部放在这里面感觉会很长,以后写一篇其他的文章详细介绍这个内容吧。

二、waveIn和WaveOut的Win32API

1.音频设备的的信息获取

首先是输入音频设备个数的获取,仅仅通过调用下面的函数就可以了,没有输入参数,输出设备个数获取的函数调用方式一样,名称略有不同。
waveInGetNumDevs(); //获取输入音频设备个数
waveOutGetNumDevs(); //获取输出音频设备个数
然后便是获取具体的设备信息了,具体调用如下面所示:

 //音频输入设备信息的获取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函数参数说明:
m_hWaveIn 表示的是音频输入的设备句柄,参数为该句柄的地址
iWaveInDevID 表示的是音频输入设备的ID,ID默认是从0开始的,如果是在不知道音频ID的话,可以将该参数设置为 WAVE_MAPPER ,即默认选择。
m_soundFormat 表示的是打开设备的格式,这个就略微复杂一点了,常用的参数有几个吧:
m_soundFormat.wFormatTag = WAVE_FORMAT_PCM; //这个是采样数据的格式,其他的咱也不懂,就用默认的 PCM 脉冲采样的格式。
m_soundFormat.nChannels = 1; //通道数
m_soundFormat.nSamplesPerSec = 11025; //采样率,常用的有11.025 kHz、22.05 kHz和44.1 kHz,其他的不建议设一些不规则的数。
m_soundFormat.nAvgBytesPerSec = 11025; //不懂,和采样率一般设置为一样的数
m_soundFormat.wBitsPerSample = 8; //表示的是数据位数,8或者16位
m_soundFormat.cbSize = 0; //一般为0
hWnd 这个是用于接收录音通知消息的句柄,填做主窗口句柄就行,注意一个类型转换。
0L 它的名字是 dwInstance,不太懂,没什么关系其实
CALLBACK_WINDOW 表示的是 dwCallback(也就是hWnd) 是个窗口句柄,指定的是 dwCallback(也就是hWnd) 参数是什么东西。给大家看一下原版的英文解释,这个有好多种内容,不想看的可以跳过了,看起来挺晦涩的:

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

输出设备的打开啊方式类似,此处也不做过多解释了,大家看一下就差不多能懂了,相信能认认真真看这篇博客的应该都是很棒的人。
(注意:此处的 m_hWaveOut 类型是 HWAVEOUT,和上面的那个不一样,注意区分哦)

 //======================== 播放 ==========================res = waveOutOpen(&m_hWaveOut, iWaveOutDevID, &m_soundFormat, (DWORD)hWnd,0L, CALLBACK_WINDOW);if (res != MMSYSERR_NOERROR)return false;

3.输入输出设备缓冲区的准备和添加

老样子了,先从音频输入设备讲起:
MAX_BUFFER_SIZE 是自己设置的一个宏定义,给大家一个大概的数量大小参考吧,10240 或者 20480 都可以的,这个其实是一个平衡,如果缓冲区过大,那么通话延迟比较高,如果比较少,则通话的连续性质量不高,自己看着试试就行
m_pWaveHdrIn.dwBytesRecorded 表示的是在准备这个缓冲区的时候,里面的初始数据占多少字节,填个 0 就行。
m_pWaveHdrIn.dwFlags 参数有好多内容,有兴趣的可以看看下面的参考内容:

方法
提供缓冲区信息的标志。定义了以下值:
WHDR_BEGINLOOP
这个缓冲区是循环中的第一个缓冲区。该标志仅用于输出缓冲区。
WHDR_DONE
由设备驱动程序设置,表示缓冲区已用完,正在将其返回给应用程序。
WHDR_ENDLOOP
这个缓冲区是循环中的最后一个缓冲区。该标志仅用于输出缓冲区。
WHDR_INQUEUE
由窗口设置,表示缓冲区已排队等待回放。
WHDR_PREPARED
由窗口设置,表示缓冲区已用波形预预热器或波形输出预预热器功能准备好。

waveInPrepareHeader 函数主要是准备音频输入设备的缓冲区(其实翻译一下看名字大概就能猜出来),大概参数介绍:
m_hWaveIn 音频输入设备的句柄
m_pWaveHdrIn 缓冲区的地址
sizeof(WAVEHDR) 这个参数么,不用多说了哈哈

下一个函数 waveInAddBuffer 也简单,不多说了,相信大家的实力。

 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;

音频输出设备的缓冲区准备和添加类似,参考下面代码:

 //准备内存块播放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;//指定数据块到音频播放缓冲区res = waveOutWrite(m_hWaveOut, &m_pWaveHdrout, sizeof(WAVEHDR));if (res != MMSYSERR_NOERROR)return false;

4.播放和录音的开始和终止

先写这几个比较简单的操作:
开始录音:waveInStart(m_hWaveIn);
停止录音:waveInStop(m_hWaveIn);
停止播放:waveOutReset(m_hWaveOut);
然后播放录音的操作略微复杂一点,需要把数据放到播放缓冲区,缓冲区的内容会自动播放:
m_cBufferout 播放缓冲区的首地址
pData 要播放的声音数据流首地址
dwDataLen 声音数据流的长度

memcpy(m_cBufferout, pData, dwDataLen);
(函数不唯一啊,这个windows有好多,例如CopyMemory也可以实现这个功能,这里用的是 memcpy 函数)

5.录音通知消息的获取和处理

开始录音后,缓冲区不断地增加捕获到的音频数据,当音频数据接受满了之后,就会向前文说的那个窗口句柄的窗口发送通知消息 MM_WIM_DATA ,收到这个消息之后程序就要对这些数据进行处理,处理完毕后最最重要一件事是清空缓冲区,windows并不会自己清理缓冲区内容。
清理缓冲区用到的函数是
waveInUnprepareHeader 这个参数其实差不多,

waveInPrepareHeader函数清理由WaveInPrepareHeader函数执行的准备。该函数必须在设备驱动程序填充缓冲区并将其返回给应用程序后调用。在释放缓冲区之前,您必须调用此函数。

这个是官方的解释,感觉这个挺详细的,放在这里大家看看。清空缓冲区之后,重新准备缓冲区,和上面的操作一样。

 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;

清空输出缓冲区的函数(程序结束,记得清空缓冲区内容):

 int res = waveOutUnprepareHeader(m_hWaveOut, &m_pWaveHdrout[0], sizeof(WAVEHDR));

6.关闭音频输入和输出设备

调用两个特别简单的函数实现最终的收尾工作,哦耶!

 if (m_hWaveIn){waveInClose(m_hWaveIn);m_hWaveIn = NULL;}if (m_hWaveOut){waveOutClose(m_hWaveOut);m_hWaveOut = NULL;}

三、通信数据包的设计以及客户端服务器逻辑

这个怎么说呢,感觉就要从实际出发了,这里简单的说一下思路吧。
功能分析:
1.客户端登陆ID分配以及其他客户端的广播
可以用静态变量++来为客户端赋值ID,以此保证每个用户ID不重复,然后广播就遍历所有的客户端。包括登陆包,反馈包,广播包。
2.拨打电话提示
这个就是拨打电话请求包和拨打电话的回复包两个是吧。
3.声音数据的传输
必须指定谁的语音信息发到哪个客户端,所以语音包必须包含发送用户的ID和接收用户的ID。
4.挂断通知
需要挂断包对吧。用户挂断情况可能是主动挂断,或者是程序异常关闭,所以挂断包可以添加一点挂断信息等等。

其他的自己看着添加就行了哈哈哈哈,谢谢大家的阅读!

socket实现局域网语音通话 c++ winapi相关推荐

  1. 局域网语音通话demo

    https://github.com/xj913492952/SocketClientVoicePhone https://github.com/eltld/SocketClientVoicePhon ...

  2. C#中利用Socket实现网络语音通信[初级版本]

    现在时下的VOIP软件很多,比较有名的就是Skype,还有其它诸如UUcall.快门等等.它们提供的功能除了网络上的语音通话外,还可以与固定电话.手机等通话.在本篇中主要介绍利用C#实现语音通信的基本 ...

  3. DirectX编程:C#中利用Socket实现网络语音通信

    现在时下的VOIP软件很多,比较有名的就是Skype,还有其它诸如UUcall.快门等等.它们提供的功能除了网络上的语音通话外,还可以与固定电话.手机等通话.在本篇中主要介绍利用C#实现语音通信的基本 ...

  4. DirectX编程:C#中利用Socket实现网络语音通信[初级版本]

    [声明:本篇来源:http://www.cnblogs.com/stg609/archive/2008/11/19/1334544.html 作者:stg609] 现在时下的VOIP软件很多,比较有名 ...

  5. Android P2P语音通话实现(思路探讨)

    最近在在研究语音通话的实现,现在把我的实现思路记录在这里.不过,由于初次接触语音通话,所以这是一个简单的思路,也是经过google以及baidu之后的一个学习总结. 我认为一个语音通话系统至少有四个模 ...

  6. Android手机间语音通话使用webrtc消除回音

    公司的产品智能门铃当与人通话过程中会产生回音,因此想用webrtc的回音消除模块来消除,所以让我写一个android间语音通话的demo来验证webrtc回音消除模块的效果,下面就是我实现这个demo ...

  7. freeswitch + webRtc +jssip 实现web端语音通话

    版权声明:本文为CSDN博主「foruok」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明. 原文链接:https://blog.csdn.net/foruok/ ...

  8. iOS语音通话(语音对讲)

    中间参考了别人的Demo,下载地址不记得了. 因为项目需要做一个语音对讲功能,其实说白了就是类似QQ的语音通话,但是资料少之又少,研究了好久,才跟同事弄出一个粗略的版本.我记性不好,所以来记录一下,也 ...

  9. 用yate2实现软VoIP语音通话(SIP协议)

    转载 用yate2实现软VoIP语音通话(SIP协议) 阳光男孩 发表于 2009-01-08 2009年1月7日,工业与信息化部发放了三张3G牌照,标志着中国进入了通信技术的新时代.3G的重要特性之 ...

最新文章

  1. Flex DataGrid可编辑对象实现Enter跳转
  2. 13 个应该记住的最不寻常的搜索引擎
  3. ECMAScript 实现继承的几种方式
  4. 【再来一套网站程序】kfguan网整站程序下载
  5. SQL server 2005安装问题汇总
  6. 上线前,一款To B产品要做哪些运营动作?
  7. 在Unity中添加图标
  8. matlab方差 anov,方差分析
  9. 计算机组成原理强制类型转换规则,计算机组成原理——浮点数加减运算强制类型转换...
  10. Linux周立功CAN驱动安装指导
  11. Serializer和ModelSerializer
  12. 广告牌定时器怎么设置时间_定时器怎么调时间
  13. 神经网络分类效果评价——多元分类交叉熵
  14. 【Photoshop】Photoshop 64Bit与Camera Raw安装包
  15. JavaEE进阶——Spring学习笔记
  16. Android全面屏最大纵横比适配
  17. mysql在触发器中调用存储过程_mysql 触发器中调用存储过程
  18. 如何高效学习 - 斯科特·扬
  19. js中文汉字按拼音排序
  20. React的Render的简单实现

热门文章

  1. QT入门基础认知(三个常用类、三种对话框类型、信号和槽)
  2. 几种常用的接口协议的积累,欢迎补充
  3. 获取 Windows 任务管理器中应用程序和进程 任务
  4. BERT-Whole Word Masked(WWM)
  5. 使用支持SPDY协议的Wireshark截包(含spdyshark插件)
  6. JCP失去活力 Java.net能否取而代之?
  7. 计算机硬件更新向导,Win7系统提示欢迎使用找到新硬件向导怎么办
  8. Android 开机向导(没有优先启动问题)
  9. 软件测试Bug评测 之Serverity(严重程度)、Priority(优先级)
  10. 三千万单车产能大跃进:供应链提价,矛盾一触即发