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 打開本地音視頻
// 收到消息:客戶端進入房間 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;
}
這個函數有系統消息回調函數來調用,而系統消息回調函數我們在初始化的時候已經設置了,下面將我們的系統消息回調函數貼出來:
// 異步消息通知回調函數定義
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(""); //清空控件
}
是的,使用BARC_SendTextMessage即可啦,參數一爲用戶id,至此,我們的功能基本完成。
3.8 有進必有出---離開房間
離開房間之後可以做什麼?我們考慮實現離開房間後,要進入其他房間而不馬上退出,因此這裏我只使用了LeaveRoom!!!
//單擊離開房間事件
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));
}
}
voidWidget::closeEvent(QCloseEvent *event)
{
BRAC_Logout();
BRAC_Release();
event->accept();
}
四、 總結
整個過程開發非常方便快捷,得益於AnyChat SDK接口的使用簡潔,尤其是音視頻打開和處理過程的簡化,全面的回調功能函數接口。本設計採用服務器爲anychat sdk提供的demo服務器,可以直接運行部署。