首页 > 代码库 > 简易视频聊天软件的快速开发(QT5.3)
简易视频聊天软件的快速开发(QT5.3)
为了在linux平台下快速开发一款视频聊天软件,本文选取了AnyChat SDK作为核心开发,开发环境使用QT5.3,下面将讲解我的开发过程。
一、 开发环境的搭建
1. 首先,虚拟机安装linux系统,我选取的是最新的Ubuntu14.04-32bit ;
2. 然后,到QT官网http://qt-project.org/downloads,下载最新的linux版本QT5.3.2,如下所示:
3. 最后,到AnyChat官网http://www.anychat.cn/download.html,下载最新的linux版本的SDK,这里我们选取32bit,如下图所示:
二、 软件功能
在这里,我只需要如下几个功能即可:
1. 能够打开本地音视频;
2. 能够获取在线用户列表;
3. 能够请求在线用户音视频,实现视频聊天;
4. 能够发送文字聊天。
三、 真正的开始
需求确定之后,我们就开始创建我们的工程啦!
1. 现在我们打开QT5软件,创建一个新工程,首先就是布局我们的UI,如下图所示:
然后我们给我们的UI写上对象名字,如下所示:
2. 添加工程依赖性(包括anychat sdk依赖文件的添加)
QT软件配置头文件和库文件有两种方法:
2.1 可以手动在.pro为后缀的文件里面进行添加;
2.2 可以右键工程项目,然后添加相应的文件和库,此方法会自动修改.pro文件,如下左右两图所示(左图为手动添加,右图为添加后的pro文件,也可以按右图修改pro文件):
其中INCLUDEPATH为头文件包含路径,这里添加anychat sdk目录;LIBS为依赖库文件,这里添加anychatcore动态库;$$PWD表示pro文件当前目录;当然也可以使用绝对路径,直接填写路径和文件名即可,按左图方法添加绝对正确。
我的anychat sdk文件如下所示:
3. 接下来我们需要编写具体功能的实现;
3.1 初始化
因为我们要使用anychat sdk,因此我们在程序初始化的时候对sdk初始化;
//设置SDK核心组件所在目录(注:demo程序只是设置为当前目录,项目中需要设置为实际路径)
QString szCoreSDKPath;
szCoreSDKPath =QCoreApplication::applicationDirPath(); //获取当前应用程序路径
(strrchr((char*)szCoreSDKPath.toStdString().c_str(),‘/‘))[1] = 0;
BRAC_SetSDKOption(BRAC_SO_CORESDK_PATH,
(char*)szCoreSDKPath.toStdString().c_str(),
strlen((char*)szCoreSDKPath.toStdString().c_str()));
// 根据BRAC_InitSDK的第二个参数:dwFuncMode,来告诉SDK该如何处理相关的任务(详情请参考开发文档)
DWORD dwFuncMode =BRAC_FUNC_VIDEO_CBDATA/*BRAC_FUNC_VIDEO_AUTODISP*/ |
BRAC_FUNC_AUDIO_AUTOPLAY |BRAC_FUNC_CHKDEPENDMODULE |
BRAC_FUNC_AUDIO_VOLUMECALC |BRAC_FUNC_NET_SUPPORTUPNP |
BRAC_FUNC_FIREWALL_OPEN|BRAC_FUNC_AUDIO_AUTOVOLUME |
BRAC_FUNC_AUDIO_VOLUMECALC |BRAC_FUNC_CONFIG_LOCALINI;
BRAC_InitSDK((HWND*)this->winId(),dwFuncMode);
BRAC_SetVideoDataCallBack(BRAC_PIX_FMT_RGB32,VideoData_CallBack,this);//设置视频数据回调
BRAC_SetAudioDataCallBack(AudioData_CallBack, this); //设置声音数据回调
BRAC_SetNotifyMessageCallBack(NotifyMessage_CallBack,this); //设置异步消息回调
BRAC_SetTextMessageCallBack(TextMessage_CallBack,this); //设置消息发送回调
SDK初始化参数设置,其中,对于视频,我们设置为BRAC_FUNC_VIDEO_CBDATA,为视频数据回调方式,后续需要我们实现视频数据的处理,这里主要是渲染显示;对于音频,我们设置为BRAC_FUNC_AUDIO_AUTOPLAY,使用SDK自动播放模式,我们不用做任何处理。
在BRAC_InitSDK函数之后我们又调用了四个API接口,分别设置视频回调、声音回调(这里实际不用设置,我们采用自动播放模式)、系统消息回调、文字信息回调(文字聊天);
至此初始化完毕!
3.2 编写登录服务器接口
voidWidget::HelloChatLogin()
{
BRAC_Connect("demo.anychat.cn",8906); //连接服务器 :connect to server
BRAC_Login("HelloChat","", 0); //登陆服务器 :loging to server
}
这里为了方便,服务器地址、端口号、用户名、密码都写进来了;当我们登录成功之后,就可以继续往下啦。
//单击进入房间事件
voidWidget::on_EnterRoom_Btn_clicked()
{
QString roomId =ui->RoomId_lineEdit->text(); //房间号
QString pwd = ""; //密码
BRAC_EnterRoom(roomId.toInt(),(LPCTSTR)pwd.toStdString().c_str() , 0); //进入房间
}
3.3 进入相应的房间
RoomId_lineEdit就是我们的ui控件,我们在编辑控件填写相应的房号即可;
3.4 刷新在线用户列表
int m_iUserID[MAX_USER_NUM]; //其他用户ID号
int m_SelfId; //自己的ID
voidWidget::HelloChatRefreshUserList()
{
//先清空列表
ui->UserlistWidget->clear();
memset(m_iUserID,-1,sizeof(MAX_USER_NUM));
//获取在线用户人数
DWORD dwUserNum = 0;
BRAC_GetOnlineUser(NULL, dwUserNum);
if(!dwUserNum)
return ;
//获取在线用户ID列表
LPDWORD lpdwUserList =(LPDWORD)malloc(sizeof(DWORD) *dwUserNum);
BRAC_GetOnlineUser(lpdwUserList,dwUserNum);//获取在线用户id
//重新入列
for(int i = 0; i < (int)dwUserNum; i++)//刷新用户列表
{
DWORD dwUserID = lpdwUserList[i];
if(dwUserID != -1)
{
char cUserName[30];
BRAC_GetUserName(dwUserID,cUserName,sizeof(cUserName));//获取用户名
m_iUserID[i] = dwUserID;
ui->UserlistWidget->insertItem(i,cUserName); //对应的用户名添加到列表中
}
else
break;
}
free(lpdwUserList);
}
我单独写了一个接口,实现由用户进入房间和离开房间时会刷新列表;
3.5 打开本地音视频
当我们成功进入房间时,我们首先打开自己的音视频,这里用到了两个API,
// 收到消息:客户端进入房间 wParam (INT)表示所进入房间的ID号,
// lParam (INT)表示是否进入房间:0成功进入,否则
// 为出错代码
longWidget::OnGVClientEnterRoom(WPARAM wParam, LPARAM lParam)
{
QString logstr;
int roomid = (int)wParam;
if(lParam == 0) //自己成功进入房间,然后打开视频和音频
{
logstr.sprintf("#INFO# success enterroom:%d,user ",roomid);
//Open Local Camera
BRAC_UserCameraControl(-1,TRUE);
BRAC_UserSpeakControl(-1,TRUE);
}
else
{
logstr.sprintf("#INFO# cannot enter room,error code: %d ",lParam);
}
emit changeSysLogs(logstr);
return 0;
}
BRAC_UserCameraControl()和BRAC_UserSpeakControl(),分别打开音视频;
这个函数有系统消息回调函数来调用,而系统消息回调函数我们在初始化的时候已经设置了,下面将我们的系统消息回调函数贴出来:
// 异步消息通知回调函数定义
void CALLBACKWidget::NotifyMessage_CallBack(DWORD dwNotifyMsg, DWORD wParam, DWORD lParam,LPVOID lpUserValue)
{
Widget* pAnyChatSDKProc= (Widget*)lpUserValue;
if(!pAnyChatSDKProc)
return;
switch(dwNotifyMsg)
{
caseWM_GV_CONNECT: pAnyChatSDKProc->OnGVClientConnect(wParam,NULL);
break;
case WM_GV_LOGINSYSTEM: pAnyChatSDKProc->OnGVClientLogin(wParam,lParam);
break;
case WM_GV_ENTERROOM: pAnyChatSDKProc->OnGVClientEnterRoom(wParam,lParam);
break;
case WM_GV_MICSTATECHANGE: pAnyChatSDKProc->OnGVClientMicStateChange(wParam,lParam);
break;
case WM_GV_USERATROOM: pAnyChatSDKProc->OnGVClientUserAtRoom(wParam,lParam);
break;
caseWM_GV_LINKCLOSE: pAnyChatSDKProc->OnGVClientLinkClose(wParam,lParam);
break;
caseWM_GV_ONLINEUSER: pAnyChatSDKProc->OnGVClientOnlineUser(wParam,lParam);
break;
case WM_GV_CAMERASTATE: pAnyChatSDKProc->OnAnyChatCameraStateChgMessage(wParam,lParam); break;
case WM_GV_ACTIVESTATE: pAnyChatSDKProc->OnAnyChatActiveStateChgMessage(wParam,lParam);
break;
case WM_GV_P2PCONNECTSTATE:pAnyChatSDKProc->OnAnyChatP2PConnectStateMessage(wParam,lParam); break;
caseWM_GV_SDKWARNING: pAnyChatSDKProc->OnAnyChatSDKWarningMessage(wParam,lParam); break;
default:
break;
}
pAnyChatSDKProc->OnAnyChatNotifyMessageCallBack(dwNotifyMsg,wParam,lParam);
};
3.6 请求在线用户视频
我们进入房间后,获取在线用户并更新列表,所谓的在线用户是指同一房间的在线用户。
// 收到当前房间的在线用户信息 wParam (INT)表示在线用户数(不包含自己)
// lParam (INT)表示房间ID
long Widget::OnGVClientOnlineUser(WPARAMwParam, LPARAM lParam)
{
QString logstr;
int onlinenum = (int)wParam;
logstr.sprintf("#INFO# the room id:%d\n#INFO# total %duser online",
lParam, onlinenum);
emit changeSysLogs(logstr);
//刷新列表
HelloChatRefreshUserList();
return 0;
}
然后我们双击列表中的用户,进行视频请求:
我的虚拟机中打不开本地视频,所以左下角没有视频,具体的实现如下:
其中g_sOpenedCamUserId为全局变量,用于保存被请求视频的用户名;
//双击列表事件,双击后请求远程用户视频
voidWidget::on_UserlistWidget_doubleClicked(const QModelIndex &index)
{
int row = ui->UserlistWidget->currentRow(); //获取所在当前列表行号
if(g_sOpenedCamUserId!=0) //先关闭正在视频
{
BRAC_UserCameraControl(g_sOpenedCamUserId,0);
BRAC_UserSpeakControl(g_sOpenedCamUserId,0);
g_sOpenedCamUserId = 0;
ui->RemoteUserlabel->clear();
}
BRAC_UserCameraControl(m_iUserID[row],1); //打开新请求用户视频
BRAC_UserSpeakControl (m_iUserID[row],1);
g_sOpenedCamUserId = m_iUserID[row]; //保存当前建立音视频连接用户id
}
音视频成功啦,接下来我们继续扩展文字消息。
BRAC_UserCameraControl(g_sOpenedCamUserId,0); //视频控制
BRAC_UserSpeakControl(g_sOpenedCamUserId,0); //音频控制
以上两个函数说明,参数1为用户id值,这个不难理解,参数2为打开和关闭控制值,建议使用true或false;在我实际开发过程中,在音视频关闭的时候,参数2设置为-1的时候,结果没有关闭成功,因为理解有误,应该设置为0。
void Widget::on_SendMsg_Btn_clicked()
{
QString message =ui->SendMsglineEdit->text();
if(g_sOpenedCamUserId==0)
{
AppendLogString("#ERROR#no userchat with you");
AppendLogString("#ERROR#pleaseRequest Chat first");
ui->SendMsglineEdit->setText("");
return ;
}
if((BRAC_SendTextMessage(g_sOpenedCamUserId, NULL,
(LPCTSTR)message.toStdString().c_str(),
message.toStdString().length()))== 0) //发送成功
{
QDateTime time =QDateTime::currentDateTime(); //获取系统当前时间
QString strTime =time.toString(" yyyy-MM-dd hh:mm:ss");
QString info ="#INFO#";
CHAR username[30];
BRAC_GetUserName(g_sOpenedCamUserId,username,sizeof(username));
AppendLogString(info+username +strTime);
AppendLogString(message);
}
ui->SendMsglineEdit->setText(""); //清空控件
}
3.7
实现发送文字消息聊天
是的,使用BARC_SendTextMessage即可啦,参数一为用户id,至此,我们的功能基本完成。
3.8 有进必有出---离开房间
离开房间之后可以做什么?我们考虑实现离开房间后,要进入其他房间而不马上退出,因此这里我只使用了LeaveRoom!!!
当然我们离开后需要关闭视频和刷新列表,对于关闭视频调用关闭音视频接口,然后设置ui-->clear和text,对于列表,调用封装好的接口即可;
//单击离开房间事件
voidWidget::on_LeaveRoom_Btn_clicked()
{
//先关闭远程用户视频
BRAC_UserCameraControl(g_sOpenedCamUserId,0);
BRAC_UserSpeakControl(g_sOpenedCamUserId,0);
ui->RemoteUserlabel->clear();
ui->RemoteUserlabel->setText("RemoteUser");
//关闭本地用户视频
BRAC_UserCameraControl(m_SelfId,0);
BRAC_UserSpeakControl(m_SelfId,0);
ui->LocalUserlabel->clear();
ui->LocalUserlabel->setText("LocalUser");
//离开当前房间
BRAC_LeaveRoom(1);
//然后清空用户列表
HelloChatRefreshUserList(); //清空用户列表
AppendLogString("#INFO# User LeaveRoom");
g_sOpenedCamUserId = 0;
}
3.9 关键的视频渲染
因为我使用了回调方式获取视频数据,所以我得手动进行视频渲染首先定义视频缓冲和大小(我这里定义为类成员):
char* m_lpLocalVideoFrame; //本地视频缓冲
int m_iLocalVideoSize; //本地视频缓冲大小
char* m_lpRemoteVideoFrame; //远程视频缓冲
int m_iRemoteVideoSize; //远程视频缓冲大小
然后在回调函数中做视频渲染:
QT中我使用QImage来加载视频数据,然后调用label控件的setPixmap方法来绘制图像,当然这个渲染方法效率一般;
//视频数据显示
void Widget::DrawUserVideo(DWORDdwUserid, LPVOID lpBuf, DWORD dwLen,
BITMAPINFOHEADERbmiHeader,Widget *pWidget)
{
int width = bmiHeader.biWidth;
int height = bmiHeader.biHeight;
//判断用户id选择不同的显示区域
if(m_SelfId == dwUserid) //本地用户视频
{
char* p = m_lpLocalVideoFrame;
if( !p ||m_iLocalVideoSize < dwLen)
{
p = (char*)realloc(p, dwLen);
if(!p)
return;
m_iLocalVideoSize = dwLen;
}
memcpy(p, lpBuf, dwLen);
QImage img = QImage((uchar*)p,width,height,QImage::Format_RGB32);
pWidget->ui->RemoteUserlabel->setPixmap(QPixmap::fromImage(img));
}
else //远程用户视频
{
char* p = m_lpRemoteVideoFrame;
if( !p ||m_iRemoteVideoSize < dwLen)
{
p = (char*)realloc(p, dwLen);
if(!p)
return;
m_iRemoteVideoSize = dwLen;
}
memcpy(p, lpBuf, dwLen);
QImage img = QImage((uchar*)p,width,height,QImage::Format_RGB32);
pWidget->ui->RemoteUserlabel->setPixmap(QPixmap::fromImage(img));
}
}
3.10
最后,我没有添加关闭按钮,而是使用窗口的关闭按钮,所以我要重载CloseEvent,在关闭时调用断开连接和释放资源。
voidWidget::closeEvent(QCloseEvent *event)
{
BRAC_Logout();
BRAC_Release();
event->accept();
}
四、 总结
整个过程开发非常方便快捷,得益于AnyChat SDK接口的使用简洁,尤其是音视频打开和处理过程的简化,全面的回调功能函数接口。本设计采用服务器为anychat sdk提供的demo服务器,可以直接运行部署。
简易视频聊天软件的快速开发(QT5.3)