Windows VoIP SDK使用文档

一、简介

VoIP 即Voice over IP,是一种通过因特网进行语音视频等数据传输的技术,本文档主要详述了在Windows 端使用VoIP SDK和OpenCV开源库进行软件开发的相关步骤。

二、工程配置

2.1开发工具

本文档所依据的开发工具是Visual Studio 2015,可以到https://www.visualstudio.com/zh-hans/进行下载,所依赖的第三方库有:

1、OpenCV2.4.13库,可以到https://opencv.org/releases.html 进行下载

2、VoIP SDK,可以到http://www.tucodec.com/ 进行下载

2.2 开发工具配置

(1) 打开Visual Studio,新建一个win32项目。

(2) 添加项目文件,把OpenCV及VoIP SDK的相关头文件加入工程目录。VoIP 所提供的头文件主要包括:network_center.h, voip_context.h, voip_define.h, voip_manager.h等。

(3) 配置项目属性,把OpenCV 及 VoIP SDK的相关.lib文件加入工程链接库。

(4) 把OpenCV及VoIP SDK的相关.dll文件拷贝到项目的输出目录下。

(5) 进行代码编写。

 

三、代码编写

3.1 工程结构

3.1 利用VoIP SDK进行开发的软件模块图

 

3.2 各个功能模块介绍

如图3.1展示了利用VoIP SDK进行软件开发的结构图,其主要模块功能介绍如下:

(1) 音视频采集模块:在Windows系统中,进行音视频采集的工具有很多,比如官方提供的开发工具诸如DirectShow、Window Media Foundation等,另外,开源项目如FFmpeg、OpenCV(针对视频采集)等都集成了采集功能

(2) 音视频渲染模块:同样的,在Windows中,对音视频渲染也具有广泛的支持,上述几种工具同时也支持音视频的渲染和播放(OpenCV只支持视频渲染)。

(3) VoIP模块:为了方便集成和使用,在此模块中囊括了音视频数据的编码与解码,音视频数据的网络传输模块(服务器模块),极大的降低了网络音视频通信的开发门槛。

本文档使用OpenCV和waveForm作为客户端采集和渲染的工具,是为了简单明了地介绍VoIP SDK的使用方法。音频相关的接口和视频的接口有很大程度上的相似,下面对VoIP的接口的调用和使用规则做详细介绍,并提供示例代码。

 

3.3 VoIP SDK的类及API

在网络上传输音视频数据需要建立通道,一般而言,两个需要通话的客户端不会处于同一个局域网内,也不太可能具有独立的公网IP,直接进行端到端通信显然不行,所以需要采用一个具有公网IP的服务器进行数据转发,我们称之为转发服务器(RelayServer)。转发服务器的程序和文档可以参见《服务端RelayServer使用说明》。

 (1) 在进行数据通信之前,两(多)个客户端需要登录到转发服务器。接口如下:

/**

* 登录转发服务器

* @param ip  转发服务器地址

* @param port  转发服务器端口,由转发服务程序配置

* @param usrID  客户端连接端口,需要商讨决定,如A选则1,B可以是1以外的数

* @param sessionID  会话ID,暂时未使用,可以默认写0

**/

Int ClientNetwork::Login(uint32_t usrID, int sessionID);

 

代码示例:

客户端A登录转发服务器:

NetworkCenter::Parameter param = {string serverIP, uint32_t port, uint32_t appKey, uint32_t appSecret}

ClientNetwork  *networkClient = ClientNetwork::CreateInstance(param);

networkClient->Login(1, 0);

 客户端B登录转发服务器:

……

networkClient->Login(2, 0);

当A和B都登录到转发服务器后可以开始数据采集与发送,通过OpenCV获取到视频数据后需要调用VoIP的API将数据传入编码整合模块,为数据的网络传输做准备,其接口如下所示。

 

(2) 视频数据传入VoIP

/**

* 将视频数据传入VoIP

* @param &sourceID   voip_define.h中的枚举类型

* @param *data  传入的视频数据指针

* @param size 传入数据大小

* @param width为视频宽度

* @param height为视频高度

* @param format视频格式,定义于voip_define.h,目前只支持YUV420P

* @param rotation 视频旋转角度,定义于voip_define.h

* @param front为前置摄像头的标识,在Windows端中默认为FALSE即可

**/

void setVoipVideoData(const void *data, uint32_t size,

uint16_t width, uint16_t height, int rotation, bool front);

示例代码:

void setVoipVideoData(

videoFrame.data,

videoFrame.width * videoFrame.height*3/2,

videoFrame.width,

videoFrame.height,

VIDOE_ROTATION_0,

FALSE

);

        为了达到实时传输的目的,客户端每采集一帧视频便会调用一次此接口,因此,此接口一般会放在视频采集的回调函数中使用。与之类似的,音频数据的送入也会采取一个相似的接口,如下所示。

 

(3) 音频数据传入VoIP

/**

* 将音频数据传入VoIP

* @param &sourceID  voip_define.h中的枚举类型

* @param *data 音频数据地址

* @param size 每一帧的数据大小

* @param samplerate为音频的采样率,目前支持16000kps

**/

void setVoipAudioData(const void *data, uint32_t size, uint32_t samplerate);

示例代码:

void setVoipAudioData(

                   audioFrame.data,

                   audioFrame.size,

                   audioFrame.sampleRate

                   );

        这里对音频数据的传递采用“音频帧”的方法,一般音频采集程序会每隔一个固定的时间(如10ms)会输出一个“音频帧”,根据音频数据的采用率、位深度和通道数等参数就可以计算出每一帧的音频数据大小,目前代码支持单声道音频数据传输。

 

以上介绍了音视频数据的传入接口,下面介绍音视频数据的传出接口。

        (4) 视频数据的获取和播放

        视频数据的获取是由VoIP主动推数据给客户端用以播放,在使用VoIP的时候客户端需要继承并实现其规定的一个视频获取接口,每当准备好一帧视频时VoIP会回调此函数把数据发送出来,其接口定义如下所示:

        /**

        * 视频数据的传出

        * @param isLocal 标志着本次传出的视频帧是否是本机视频

        * @param image 包含了视频帧的各个属性,包括宽、高、格式、数据等

        * @param usrID标志着本次传出的视频帧对应的usrID,对应接口(1)中的usrID

        **/

 

        virtual  void onVideoFrame(bool isLocal, std::shared_ptr image,

 uint32_t usrID) = 0;

        这个接口类是给VoIP进行回调,客户端应该对其进行实现,样例代码可以参考:

        void onVideoFrame(bool isLocal, std::shared_ptr image,

                                                       uint32_t usrID)

{

        VOIP::RawImageParameter param = image->getRawImageParameter();

        int videoWidth = param.width;

        int videoHegiht = param.height;

        int videoFormat = PIX_FMT_YUV420; // 默认为YUV420P

        int videoRotation = param.position ? param.rotation : VIDEO_ROTATION_0;

        uint8_t * videoData = (uint8_t*)malloc(videoWidth*videoHeight*3/2*sizeof(uint8_t));

        memcpy(videoData, image->getImageData(), videoWidth*videoHeight*3/2);

        RenderOneFrame(videoData, videoWidth, videoHeight, videoFormat, videoRotation, isLocal, usrID);

        free(videoData);

}

RenderOneFrame为客户端的视频渲染接口,采用OpenCV可以非常方便地实现出来,另外,建议把这个函数放在另外一个专门的线程做视频渲染,以免阻塞VoIP的回调。

 

(5) 音频数据的获取和播放

音频数据的获取和播放不同于视频帧,原因在于音频的播放不能有丝毫停顿,不然会导致间断音,非常难受。因而这里获取音频采取的方式是客户端主动从VoIP中拉取音频数据并且设置音频双缓冲buffer,以防间断音。音频的拉取调用接口如下:

/**

* 拉取音频数据

* @param sourceID 为枚举类型,定义在voip_define.h中

* @param size为每次拉取的数据大小,这个值跟音频帧的大小有关,现在是320定值

**/

std::shared_ptr getVoipAudioData(const std::string &sourceID, uint32_t size);

示例代码:

void GetAudioData(void)

{

     int size = 320;

     uint8_t * audioData = (uint8_t*)malloc(size*sizeof(uint8));

     std::shared_ptr buf =getVoipAudioData(size);

             memcpy(audioData, buf->getSoundData(), size);

        }

        上面的代码简单表示了如何利用此接口从VoIP中拉取数据,播放音频的工具有很多,比如DirectSound、waveOut等,可以在开发过程中选择自己所擅长的。示例代码中没有体现双缓冲,因为有的流音频播放API自带缓冲队列,对于没有缓冲功能的API可以自己以这个函数为中心实现一个双缓冲播放队列也不是难事儿。

 

介绍完音视频数据的获取和送入的相关接口,下面介绍VoIP控制音视频数据传输的方法及服务器相关的部分接口。如果把音视频传输比作高速公路的话,那么数据就是跑在这条公路上的车辆,而数据的获取和送入的接口便是高速公路出口和入口。但是,从A端到B端的具体路径如何确定呢?这就需要转发服务器从中协商。为了方便开发者进行开发,我们把转发服务器的接口封装成几个简单易用的API提供给大家,具体如下所示。

 (6) 加入和删除节点

/**

        * @param targetUsrID 目标用户的ID,即接口(1)中定义的ID

        **/

        void ClientNetwork::AddGroupClientNode(uint32_t targetUsrID);

        void ClientNetwork::RemoveGroupClientNode(uint32_t  targetUsrID);

        如前文所述,两(多)个分别处于不同局域网内的客户端之间的通讯需要转发服务器作为中间桥梁,在客户端同时登陆到转发服务器之后,服务器已经获取到了每一个端详细信息,如IP,端口等,就好比是有了一个地图并且把数据的出发点和目的地全部标明了一样直观,现在客户端A所需要做的就是调用此API告诉转发服务器要和B连接,那么路径规划的工作就都交给服务器吧,当A和B同时添加了对方为转发目标后,通信路线就已经可以确定下来了,剩下的就是打开告诉公路的栏杆,开始通信吧。

还是以A和B两个客户端为例,提供示例代码:(删除节点与之类似)

客户端A(usrID = 1)添加客户端B(usrID = 2)为目标

ClientNetwork *networkClient = ClientNetwork::GetInstanc();

networkClient->AddGroupClientNode(2);

同时客户端B(usrID = 2)添加客户端A(usrID = 1)为目标

ClientNetwork *networkClient = ClientNetwork::GetInstance();

networkClient->AddGroupClientNode(1);

 

开始打开通路之前需要做最后的准备,将上述的几个模块进行注册与连接,VoIP的类里面提供了相应的接口,如下所示:

(7) 整合注册网络模块

/**

* 注册网络模块

* @param username 用户的昵称,可以写一个string表示自己的名字

* @param userId 用户的ID,对应于API (1)中的usrID

* @param *networkCenter 是前文ClientNerwork的父类,VoIP用之以规范接口

**/

void startGroupCallByCenter(const std::string userName,

uint32_t userId,

NetworkCenter *networkCenter);

以客户端A为例,样例代码:

ClientNetwork *networkClient = ClientNetwork::GetInstance();

VOIP::TYVoip *voip = VOIP::TYVoip::createInstance(…,  …);

Voip->startGroupCallByCenter(“A’s name”, 1, voip);

       

        通过以上步骤,现在的通信系统应该已经可以运行起来了,有始必有终,下面介绍一下结束通信系统的相关接口。

        (8) 结束通话

/*

* 关闭通话,会顺序停止内部回路,并析构掉内部变量,用户要保证在调用时,不会 *再调用输入以及音视频渲染回调。

*/

virtual void stopCall() = 0;

 

        (9) 在VoIP中移除节点

        /**

        * @param userId 用户的ID,与上文对应

        **/

        void removeGroupClientNode(const uint32_t userId);

        样例代码,客户端A(usrID为1)删除客户端B(usrID为2):

        VOIP::TYVoip *voip = VOIP::TYVoip::createInstance(…,  …);

        voip-> removeGroupClientNode(2);

 

四、结语

以上大致梳理了使用图鸭科技公司提供的VoIP SDK进行音视频通信软件的开发步骤,简单介绍了客户端调用VoIP相关API的实现音视频通讯的具体方式,其他涉及到类或API请参考图鸭科技官方提供的其他文档,祝生活愉快!