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

目录
  • 一、socket通信
  • 二、waveIn和WaveOut的Win32API
    • 1.音频设备的的信息获取
    • 2.音频设备的初始化
    • 3.输入输出设备缓冲区的准备和添加
    • 4.播放和录音的开始和终止
    • 5.录音通知消息的获取和处理
    • 6.关闭音频输入和输出设备
  • 三、通信数据包的设计以及客户端服务器逻辑

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

因为这边没办法上传视频,所以只能录制一个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.挂断通知 需要挂断包对吧。用户挂断情况可能是主动挂断,或者是程序异常关闭,所以挂断包可以添加一点挂断信息等等。

(0)

相关推荐

  • C++ 中 socket编程实例详解

    C++ 中 socket编程实例详解 sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW):基于TCP的socket编程是采用的流式套接字.在这个程序中,将两个工程添加到一个工作区.要链接一个ws2_32.lib的库文件. 服务器端编程的步骤: 1:加载套接字库,创建套接字(WSAStartup()/socket()): 2:绑定套接字到一个IP地址和一个端口上(bind()): 3:将套接字设置为监听模式

  • C++自定义封装socket操作业务类完整实例

    本文实例讲述了C++自定义封装socket操作业务类.分享给大家供大家参考,具体如下: Linux下C++封装socket操作的工具类(自己实现) socketconnector.h #ifndef SOCKETCONNECTOR_H #define SOCKETCONNECTOR_H #include "global.h" using namespace std; class SocketConnector { public: typedef enum { ENormal, EOth

  • 浅谈C++ Socket编程

    sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW):基于TCP的socket编程是采用的流式套接字. 服务器端编程的步骤: 1:加载套接字库,创建套接字(WSAStartup()/socket()): 2:绑定套接字到一个IP地址和一个端口上(bind()): 3:将套接字设置为监听模式等待连接请求(listen()): 4:请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept())

  • C++中Socket网络编程实例详解

    C++中Socket网络编程实例详解 现在几乎所有C/C++的后台程序都需要进行网络通讯,其实现方法无非有两种:使用系统底层socket或者使用已有的封装好的网络库.本文对两种方式进行总结,并介绍一个轻量级的网络通讯库ZeroMQ.  1.基本的Scoket编程 关于基本的scoket编程网络上已有很多资料,作者在这里引用一篇文章中的内容进行简要说明. 基于socket编程,基本上就是以下6个步骤: 1.socket()函数 2.bind()函数 3.listen().connect()函数 4

  • C++ socket实现miniFTP

    本文实例为大家分享了C++ socket实现miniFTP的方法,供大家参考,具体内容如下 客户端: 服务端: 建立连接 连接使用 TCP 连接,服务器和客户端分别创建自己的套接字一端,服务器等待连接,客户端发起连接(并指定服务器 ip).在两者端口号一致且不被占用的情况下,连接建立.         在整个过程中,服务器对每一个来访的客户端建立一个连接,在客户未请求与服务器断开时,该连接一直存在,用户可以不断向服务器发出请求.(持久性.流水线型连接 )         客户端断开后,关闭客户端

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

    目录 一.socket通信 二.waveIn和WaveOut的Win32API 1.音频设备的的信息获取 2.音频设备的初始化 3.输入输出设备缓冲区的准备和添加 4.播放和录音的开始和终止 5.录音通知消息的获取和处理 6.关闭音频输入和输出设备 三.通信数据包的设计以及客户端服务器逻辑 前几天看书了解了语音通话的原理,就很想自己尝试一下,然后就——做出来了,嘻嘻,先看看效果吧! 因为这边没办法上传视频,所以只能录制一个gif大家看一下效果,但是是可以听到声音的. 源码下载链接:http://

  • C#使用Socket实现局域网聊天

    本文实例为大家分享了C#使用Socket实现局域网聊天的具体代码,供大家参考,具体内容如下 先运行一个java写的局域网聊天,效果图如下 后使用c#图形修改如下: C#代码: servlet服务端 using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; using System.Net; using System.Net.Sockets; using Sys

  • Swift仿微信语音通话最小化时后的效果实例代码

    前言 最近碰到个需求,需要仿微信语音通话缩小化后,保持界面最上层有一个悬浮的小View可以一点击就把刚刚缩放掉的界面再放回来,其实本质就是创造了一个新的Window,在这个window上创建了一个rootController并展示他,缩小化时是把controller dismiss掉了,再次点击那个小View之后把这个controller再展示出来便可以了.同理微信小程序其实也是在一个新的Window中做了一套新的逻辑.随着现在手机性能的提升,多Window同时存在并不会造成严重卡顿,而衍生出来

  • java socket实现局域网聊天

    使用socket实现局域网聊天,写这个主要是为了深入理解socket与信息流在网络中的传送过程加深理解. 代码很简单分为两个类,一个服务器类,一个客户端,运行时分别启动两个线程一个负责接受另一个负责发送. 整体流程是 两台机器分别启动程序,一个选择主动连接 另一个选择被动接受,即可实现类似qq的聊天效果两个人可以同时发送消息. 注意的事,不要在同一台电脑上同时启动两个,这样会服务端和客户端的ip都是相同很有可能造成自己发送的消息自己接受到了,可以两台电脑或者虚拟机里面进行. 服务端: packa

  • python编写简易聊天室实现局域网内聊天功能

    本文实例为大家分享了python实现局域网内聊天功能的具体代码,供大家参考,具体内容如下 功能: 可以向局域网内开启接收信息功能的ip进行发送信息,我们可以写两段端口不同的代码来实现在一台电脑上与自己聊天. 关键点: 要想实现此功能必须将程序的端口固定 from socket import * def udp_send(udp_socket): # 发送消息 接收用户输入内容 send_mes = input("请输入发送内容:") # 接收用户输入ip ip = input(&quo

  • Java实现局域网聊天室功能(私聊、群聊)

    本文实例为大家分享了Java实现局域网聊天室功能的具体代码,供大家参考,具体内容如下 Server 服务端 import java.io.IOException; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket;   /**  * 服务端  */ public class Server {       private static final int SERVER_PORT=8080

  • iOS开发微信收款到账语音提醒功能思路详解

    一.背景 为了解决小商户老板们在频繁交易中不方便核对.确认到账的痛点,产品MM提出了新版本需要支持收款到账语音提醒功能.这篇文章总结了开发过程中遇到的坑和一些小技巧. 二.技术方案 后台唤醒App 收款到账语音提醒需要收款方在收到款后,播放一段TTS合成语音播报金额,微信在前台时可以通过模板消息将需要播报的金额带下来,再请求TTS数据并播放,但是app在挂起或者被kill掉的情况下要如何请求语音数据并播放呢? iOS提供了两种方式唤醒处于挂起或已经被kill掉的app.分别是Silent Not

  • Android仿微信语音聊天功能

    本文实例讲述了Android仿微信语音聊天功能代码.分享给大家供大家参考.具体如下: 项目效果如下: 具体代码如下: AudioManager.java package com.xuliugen.weichat; import java.io.File; import java.io.IOException; import java.util.UUID; import android.media.MediaRecorder; public class AudioManager { private

  • Android 基于百度语音的语音交互功能(推荐)

    项目里面用到了语音唤醒功能,前面一直在用讯飞的语音识别,本来打算也是直接用讯飞的语音唤醒,但是讯飞的语音唤醒要收费,试用版只有35天有效期.只好改用百度语音,百度语音所有功能免费,功能也比较简单实用,包括语音识别,语音合成和语音唤醒,正好可以组成一套完整的语音交互功能. 效果图: 首先是语音唤醒功能,说出关键词即可叫语音识别,唤醒成功会有语音提示,这里采用了百度语音的合成功能.然后百度语音识别会根据wifi情况自动切换在线或者离线识别,但是离线识别只能识别已经导入的关键词,而且离线第一次识别需要

  • Java Socket实现的传输对象功能示例

    本文实例讲述了Java Socket实现的传输对象功能.分享给大家供大家参考,具体如下: 前面两篇文章介绍了怎样建立Java Socket通信,这里说一下怎样使用Java Socket来传输对象. 首先需要一个普通的对象类,由于需要序列化这个对象以便在网络上传输,所以实现java.io.Serializable接口就是必不可少的了,如下: package com.googlecode.garbagecan.test.socket.sample3; public class User implem

随机推荐