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++ 类模板、函数模板全特化、偏特化的使用

    C++ 类模板、函数模板全特化、偏特化的使用

    这篇文章主要介绍了C++ 类模板、函数模板全特化、偏特化的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • C++类与对象之日期类的实现

    C++类与对象之日期类的实现

    这篇文章主要介绍如何实现C++中的日期类相关资料,需要的朋友可以参考下面文章的具体内容
    2021-09-09
  • c++难以发现的bug(有趣)

    c++难以发现的bug(有趣)

    这篇文章主要介绍了c++难以发现的bug(有趣)的相关资料,需要的朋友可以参考下
    2017-10-10
  • 详解C++中虚析构函数的作用及其原理分析

    详解C++中虚析构函数的作用及其原理分析

    这篇文章主要介绍了C++中虚析构函数的作用及其原理分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • 关于vector的常见用法详解

    关于vector的常见用法详解

    这篇文章主要介绍了关于vector的常见用法详解,vector本身可以作为数组使用,而且在一些元素个数不确定的场合可以很好地节省空间,本文给大家介绍的非常详细,需要的朋友可以参考下
    2023-02-02
  • 深入学习C++中的函数概念

    深入学习C++中的函数概念

    这篇文章主要介绍了C++中的函数概念,是C++入门学习中的基础知识,需要的朋友可以参考下
    2015-09-09
  • C++ Boost Foreach超详细分析讲解

    C++ Boost Foreach超详细分析讲解

    Boost是为C++语言标准库提供扩展的一些C++程序库的总称。Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一,是为C++语言标准库提供扩展的一些C++程序库的总称
    2022-11-11
  • 基于C语言实现简单五子棋游戏

    基于C语言实现简单五子棋游戏

    这篇文章主要为大家详细介绍了基于C语言实现简单五子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • C语言中 & 和 &&的区别详解

    C语言中 & 和 &&的区别详解

    这篇文章主要介绍了C语言中 & 和 &&的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • QT .pro文件的实现

    QT .pro文件的实现

    本文主要介绍了QT .pro文件的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01

最新评论