IM開發流程


AnyChat SDK的使用及簡易視頻聊天軟件的快速開發(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服務器,可以直接運行部署。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章