首页 > 代码库 > 简易视频聊天软件的快速开发(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)