C++用winapi socket实现局域网语音通话功能

 更新时间:2022年06月20日 10:25:35   作者:治治棒棒哒  
这篇文章主要介绍了socket实现局域网语音通话 c++ winapi,功能介绍支持录音设备查找以及播放设备查找,支持局域网语音通话,通话包含语音来电提醒和挂断电话的提示信息,还能实时的获取在线用户的数量以及对应的id,需要的的朋友一起看看

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

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

源码下载链接:http://xiazai.jb51.net/202206/yuanma/luyinj_jb51.rar

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

一、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.挂断通知 需要挂断包对吧。用户挂断情况可能是主动挂断,或者是程序异常关闭,所以挂断包可以添加一点挂断信息等等。

相关文章

  • C语言详细讲解strcpy strcat strcmp函数的模拟实现

    C语言详细讲解strcpy strcat strcmp函数的模拟实现

    这篇文章主要介绍了怎样用C语言模拟实现strcpy与strcat和strcmp函数,strcpy()函数是C语言中的一个复制字符串的库函数,strcat()函数的功能是实现字符串的拼接,strcmp()函数作用是比较字符串str1和str2是否相同
    2022-05-05
  • 用C语言实现自动售货机

    用C语言实现自动售货机

    这篇文章主要为大家详细介绍了用C语言实现自动售货机,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • C指针原理教程之AT&T汇编

    C指针原理教程之AT&T汇编

    AT&T 汇编是一种和intel汇编在语法上完全不同的汇编语言,为避免混淆intel语法,本文只介绍AT&T汇编,AT&T的第一个特点就是每个寄存器名前必须加‘%’,立即数前必须加‘$’
    2019-02-02
  • C语言数组入门之数组的声明与二维数组的模拟

    C语言数组入门之数组的声明与二维数组的模拟

    这篇文章主要介绍了C语言数组入门之数组的声明与二维数组的模拟,数组学习的同时也要相应理解C语言指针的作用,需要的朋友可以参考下
    2015-12-12
  • C++实例代码详解友元函数

    C++实例代码详解友元函数

    采用类的机制后实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,成员函数一般定义为公有的,依此提供类与外界间的通信接口。但是,有时需要定义一些函数,这些函数不是类的一部分,但又需要频繁地访问类的数据成员,这时可以将这些函数定义为该类的友元函数
    2022-06-06
  • C++ OpenCV实现物体尺寸测量示例详解

    C++ OpenCV实现物体尺寸测量示例详解

    本文主要介绍了利用OpenCV对物体的尺寸进行测量,即先定位到待测物体的位置,然后测量物体的宽高。感兴趣的同学可以跟随小编一起学习学习
    2022-01-01
  • C++ 基类指针和子类指针相互赋值的实现方法

    C++ 基类指针和子类指针相互赋值的实现方法

    下面小编就为大家带来一篇C++ 基类指针和子类指针相互赋值的实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-12-12
  • Matlab绘制酷炫坐标区域的方法详解

    Matlab绘制酷炫坐标区域的方法详解

    这篇文章主要为大家详细介绍了如何利用Matlab编写一个能让坐标区域变得很炫酷的修饰函数,文中的示例代码讲解详细,感兴趣的可以了解一下
    2022-05-05
  • C语言中break与continue的用法和区别详解

    C语言中break与continue的用法和区别详解

    当我们使用while或for循环时,如果想提前结束循环(在不满足结束条件的情况下结束循环),可以使用break或continue关键字,这篇文章主要给大家介绍了关于C语言中break与continue的用法和区别的相关资料,需要的朋友可以参考下
    2021-10-10
  • 一文教你Qt如何操作SQLite数据库

    一文教你Qt如何操作SQLite数据库

    Sqlite 数据库作为 Qt 项目开发中经常使用的一个轻量级的数据库,可以说是兼容性相对比较好的数据库之一。本文为大家介绍了Qt操作SQLite数据库的具体方法,希望对大家有所帮助
    2023-03-03

最新评论