一、pjproject库编译
0.安装调试环境:WindowsXP + vc2008, 高版本vc打开项目文件有问题。
1.pjproject官网 http://www.pjsip.org/download.htm
2.将D:\pjproject-2.3\pjlib\include\pj\config_site_sample.h另存为config_site.h
3.安装Microsoft DirectX SDK
4.将vc的连接器->常规->附加库目录加入C:\Program Files\Microsoft DirectX SDK (February 2010)\Lib\x86
5. 附加头文件目录:
../../pjsip/include,../../pjlib/include,../../pjlib-util/include,../../pjmedia/include,../../pjnath/include
6.附加库目录:
C:\Program Files\Microsoft DirectX SDK (February 2010)\Lib\x86
7.附加依赖项:
iphlpapi.lib dsound.lib dxguid.lib netapi32.lib mswsock.lib ws2_32.lib odbc32.lib odbccp32.lib oleaut32.lib uuid.lib ole32.lib user32.lib
8.忽略特定库:
MSVCRT.LIB
二、简单示例
1. 头文件依赖:
#include <ddeml.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
#include "pjsua-lib/pjsua.h"
2. WindowMain.h
#pragma once
#define TIME_CHECK WM_USER + 1001
#define TIME_ANSWER WM_USER + 1002
#define TIMESPACE 5000
#define APPNAME _T("SRPhone")
#define TOPICNAME _T("Phone")
#define STRSIZE 128
typedef struct DDEDATA{
HSZ hsz;
int data;
TCHAR name[STRSIZE];
BYTE sData[STRSIZE];
}DdeData;
class CWindowMain
{
public:
HWND m_hWnd;
HINSTANCE m_hInstance;
pjsua_acc_id m_acc_id;
pjsua_call_id m_call_id;
TCHAR m_serip[STRSIZE];
TCHAR m_usname[STRSIZE];
TCHAR m_psd[STRSIZE];
int m_iCall;
int m_iAnswer;
DWORD m_idDde;
HDDEDATA m_DdeReg;
DdeData m_app;
DdeData m_topic;
DdeData *m_items;
size_t m_nItemNum;
public:
CWindowMain();
BOOL Create();
BOOL Show();
LRESULT OnDestroy(UINT message, WPARAM wParam, LPARAM lParam);
LRESULT OnCreate(UINT message, WPARAM wParam, LPARAM lParam);
LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);
LRESULT OnTimer(UINT message, WPARAM wParam, LPARAM lParam);
BOOL DdeInit();
BOOL DdeDestroy();
HDDEDATA DdeConnect(UINT wFmt, HSZ Topic, HSZ Item, HDDEDATA hData);
HDDEDATA DdePoke(UINT wFmt, HSZ Topic, HSZ Item, HDDEDATA hData);
HDDEDATA DdeAdvreq(UINT wFmt, HSZ Topic, HSZ Item, HDDEDATA hData);
BOOL pjInit();
BOOL pjDestroy();
static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata);
static void on_call_state(pjsua_call_id call_id, pjsip_event *e);
static void on_call_media_state(pjsua_call_id call_id);
private:
static HDDEDATA CALLBACK DdeCallback(UINT, UINT, HCONV, HSZ, HSZ, HDDEDATA, DWORD, DWORD);
static CWindowMain * m_this;
static LRESULT CALLBACK FirstWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK DWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
};
3. WindowMain.CPP
#include "StdAfx.h"
#include "WindowMain.h"
CWindowMain * CWindowMain::m_this = NULL;
LRESULT CALLBACK CWindowMain::FirstWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){
m_this->m_hWnd = hWnd;
SetWindowLong(hWnd, GWLP_WNDPROC, (LONG)DWndProc);
return DefWindowProc(hWnd, message, wParam, lParam);
}
LRESULT CALLBACK CWindowMain::DWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){
m_this->WndProc(message, wParam, lParam);
return DefWindowProc(hWnd, message, wParam, lParam);
}
BOOL CWindowMain::Create(){
TCHAR *szWindowClass = MAKEINTATOM(FirstWndProc);
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = FirstWndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = m_hInstance;
wcex.hIcon = LoadIcon(m_hInstance, MAKEINTRESOURCE(32512));
wcex.hIconSm = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szWindowClass;
RegisterClassEx(&wcex);
CreateWindow(szWindowClass, NULL, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, m_hInstance, NULL);
return TRUE;
}
BOOL CWindowMain::Show(){
ShowWindow(m_hWnd, SW_SHOW);
UpdateWindow(m_hWnd);
return TRUE;
}
CWindowMain::CWindowMain(){
m_this = this;
m_hInstance = GetModuleHandle(NULL);
}
LRESULT CWindowMain::OnCreate(UINT message, WPARAM wParam, LPARAM lParam){
if (!DdeInit()){
PostQuitMessage(0);
}
memset(m_serip, 0, STRSIZE);
memset(m_usname, 0, STRSIZE);
memset(m_psd, 0, STRSIZE);
GetPrivateProfileString(_T("TCP/IP"), _T("serverip"), NULL, m_serip, STRSIZE, _T(".//cfg.ini"));
GetPrivateProfileString(_T("TCP/IP"), _T("username"), NULL, m_usname, STRSIZE, _T(".//cfg.ini"));
GetPrivateProfileString(_T("TCP/IP"), _T("psd"), NULL, m_psd, STRSIZE, _T(".//cfg.ini"));
if(m_serip[0] == 0 || m_usname == 0 || m_psd[0] == 0){
PostQuitMessage(0);
}
if (!pjInit()){
PostQuitMessage(0);
}
return TRUE;
}
LRESULT CWindowMain::OnDestroy(UINT message, WPARAM wParam, LPARAM lParam){
DdeDestroy();
PostQuitMessage(0);
return TRUE;
}
LRESULT CWindowMain::WndProc(UINT message, WPARAM wParam, LPARAM lParam){
switch (message){
case WM_CREATE:
OnCreate(message, wParam, lParam);
break;
case WM_TIMER:
OnTimer(message, wParam, lParam);
break;
case WM_DESTROY:
OnDestroy(message, wParam, lParam);
break;
}
return 0;
}
BOOL CWindowMain::DdeInit(){
//DDE ID
m_idDde = NULL;
m_DdeReg = NULL;
m_items = NULL;
m_nItemNum = 0;
//m_app初始化
m_app.hsz = NULL;
m_app.data = 0;
memset(m_app.sData, 0, STRSIZE);
_tcsncpy_s((TCHAR *)m_app.name, STRSIZE, APPNAME, STRSIZE - 1);
//m_topic初始化
m_topic.hsz = NULL;
m_topic.data = 0;
memset(m_topic.sData, 0, STRSIZE);
_tcsncpy_s((TCHAR *)m_topic.name, STRSIZE, TOPICNAME, STRSIZE - 1);
//m_items初始化
TCHAR *sitems[] = {
_T("FreeSwitchStatus"),
_T("SRPhoneStatus"),
_T("SIPHardWareStatus"),
_T("CallNumber"),
_T("Call"),
_T("AnswerNumber"),
_T("Answer"),
_T("End"),
};
m_nItemNum = sizeof(sitems)/sizeof(TCHAR *);
m_items = new DdeData[m_nItemNum];
for (size_t i = 0; i < m_nItemNum; i++){
m_items[i].hsz = NULL;
m_items[i].data = 0;
memset(m_items[i].sData, 0, STRSIZE);
_tcsncpy_s((TCHAR *)m_items[i].name, STRSIZE, sitems[i], STRSIZE - 1);
}
//|CBF_SKIP_CONNECT_CONFIRMS|CBF_FAIL_SELFCONNECTIONS|CBF_FAIL_POKES|CBF_FAIL_EXECUTES
UINT uDdeInit = DdeInitialize(&m_idDde, (PFNCALLBACK)DdeCallback, APPCMD_FILTERINITS, 0);
if(uDdeInit != DMLERR_NO_ERROR){
//如果函数执行成功则会返回一个0值,DMLERR_NO_ERROR
//如果错误则会返回非零值,用以标示它的出错类型
//DDE SERVER初始化失败
return FALSE;
}
m_app.hsz = DdeCreateStringHandle(m_idDde, m_app.name, 0);
if(m_app.hsz == 0){
return FALSE;
}
m_topic.hsz = DdeCreateStringHandle(m_idDde, m_topic.name, 0);
if(m_topic.hsz == 0){
return FALSE;
}
for (size_t i = 0; i < m_nItemNum; i++){
m_items[i].hsz = DdeCreateStringHandle(m_idDde, m_items[i].name, 0);
if(m_items[i].hsz == 0){
return FALSE;
}
}
//注册服务
m_DdeReg = DdeNameService(m_idDde, m_app.hsz, NULL, DNS_REGISTER);
if(m_DdeReg == 0){
return FALSE;
}
return TRUE;
}
BOOL CWindowMain::DdeDestroy(){
if (m_DdeReg != 0){
DdeNameService(m_idDde, 0, 0, DNS_UNREGISTER);
}
if (m_app.hsz != NULL){
DdeFreeStringHandle(m_idDde, m_app.hsz);
}
if (m_topic.hsz != NULL){
DdeFreeStringHandle(m_idDde, m_topic.hsz);
}
for (size_t i = 0; i < m_nItemNum; i++){
if (m_items[i].hsz != NULL){
DdeFreeStringHandle(m_idDde, m_items[i].hsz);
}
}
if (m_idDde != NULL){
DdeUninitialize(m_idDde);
}
if (m_items != NULL)
{
delete m_items;
}
return TRUE;
}
HDDEDATA CWindowMain::DdeCallback(UINT wType, UINT wFmt, HCONV hConv, HSZ Topic,
HSZ Item, HDDEDATA hData, DWORD lData1, DWORD lData2)
{
switch(wType){
case XTYP_ADVSTART: //通知服务器,一个客户端正在就(hsz1和hsz2表示话题和条目)发起一个报告循环,使报告循环有效
return (HDDEDATA)TRUE;
case XTYP_ERROR: //DDE已出现严重错误
return (HDDEDATA)FALSE;
case XTYP_CONNECT: //XTYP_CONNECT响应于客户程序使用的DdeConnect()函数
return m_this->DdeConnect(wFmt, Topic, Item, hData);
case XTYP_POKE: //客户端向服务器发送数据,服务器接收数据的地方
return m_this->DdePoke(wFmt, Topic, Item, hData);
case XTYP_ADVREQ: //服务器主动向客户端发送数据
return m_this->DdeAdvreq(wFmt, Topic, Item, hData);
case XTYP_REQUEST:
/*
XTYP_REQUEST响应客户程序中使用DdeClientTransaction()函数的XTYP_REQUEST和选项
XTYP_EXECUTE响应客户程序中使用DdeClientTransaction()函数的XTYP_EXECUTE和选项
在服务程序中,对于XTYP_REQUEST选项,可以用DdeCreateDataHandle函数向客户程序发送数据,而XTYP_EXECUTE则不能
XTYP_EXECUTE选项,可以用DdeGetData()函数从客户获取数据,而XTYP_REQUEST则不能。
如果返回NULL,客户端接收到 DDE_FNOTPROCESSED
*/
return (HDDEDATA)TRUE;
}
return (HDDEDATA)FALSE;
}
HDDEDATA CWindowMain::DdeConnect(UINT wFmt, HSZ topic, HSZ item, HDDEDATA hData){
TCHAR serverName[STRSIZE];
TCHAR topicName[STRSIZE];
#ifdef UNICODE
//获得服务名称字符串
DdeQueryString(m_idDde, item, serverName, STRSIZE - 1, CP_WINUNICODE);
//获得话题名称字符串
DdeQueryString(m_idDde, topic, topicName, STRSIZE - 1, CP_WINUNICODE);
#else
DdeQueryString(m_idDde, item, serverName, STRSIZE - 1, CP_WINANSI);
DdeQueryString(m_idDde, topic, topicName, STRSIZE - 1, CP_WINANSI);
#endif
if(_tcscmp(serverName, m_app.name) != 0){
return (HDDEDATA)FALSE;
}
if(_tcscmp(topicName, m_topic.name) != 0){
return (HDDEDATA)FALSE;
}
//验证通过,允许连接
return (HDDEDATA)TRUE;
}
//客户端向服务器发送数据,服务器接收数据的地方
HDDEDATA CWindowMain::DdePoke(UINT wFmt, HSZ topic, HSZ item, HDDEDATA hData){
size_t i = 3;
for (i = 3; i < m_nItemNum; i++){
if(item == m_items[i].hsz){
break;
}
}
if (i == 8)
{
return (HDDEDATA)DDE_FACK;
}
//取得数据
DdeGetData(hData, m_items[i].sData, STRSIZE - 1, 0);
if(_tcscmp(m_items[i].name, _T("Call")) == 0){
int ai = atoi((char *)m_items[i].sData);
if (ai == 1)
{
//Call CallNumber m_items[3].sData;
if (pjsua_call_get_count() > 0)
{
return (HDDEDATA)DDE_FACK;
}
char url[STRSIZE] = {0};
sprintf(url, "sip:%s@%s", m_items[3].sData, m_serip);
pj_status_t status;
status = pjsua_verify_url(url);
if (status != PJ_SUCCESS){
return (HDDEDATA)DDE_FACK;
}
pj_str_t uri = pj_str(url);
status = pjsua_call_make_call(m_acc_id, &uri, 0, NULL, NULL, NULL);
if (status != PJ_SUCCESS){
return (HDDEDATA)DDE_FACK;
}
SetTimer(m_hWnd, TIME_ANSWER, 1000, NULL);
}
return (HDDEDATA)DDE_FACK;
}
if(_tcscmp(m_items[i].name, _T("Answer")) == 0){
int ai = atoi((char *)m_items[i].sData);
if (ai == 1)
{
//Answer AnswerNumber m_items[5].sData;
KillTimer(m_hWnd, TIME_ANSWER);
pjsua_call_hangup_all();
pjsua_call_answer(m_call_id, 200, NULL, NULL);
}
return (HDDEDATA)DDE_FACK;
}
if(_tcscmp(m_items[i].name, _T("End")) == 0){
int ai = atoi((char *)m_items[i].sData);
if (ai == 1)
{
//End
KillTimer(m_hWnd, TIME_ANSWER);
pjsua_call_hangup_all();
}
return (HDDEDATA)DDE_FACK;
}
return (HDDEDATA)DDE_FACK;
}
//服务器主动向客户端发送数据
LRESULT CWindowMain::OnTimer(UINT message, WPARAM wParam, LPARAM lParam){
switch (wParam){
case TIME_ANSWER:
if (m_iCall++ >= 60)
{
KillTimer(m_hWnd, TIME_ANSWER);
pjsua_call_hangup_all();
break;
}
sndPlaySound(_T("ringin.wav"), SND_ASYNC);
break;
case TIME_CHECK:
{
pjsua_acc_info info;
pjsua_acc_get_info(m_acc_id, &info);
//FreeSwitch状态
//在线
if(info.status == PJSIP_SC_OK)
{
strncpy_s((char *)m_items[0].sData, STRSIZE, _T("1"), STRSIZE - 1);
}
//离线
if (info.status == PJSIP_SC_FORBIDDEN)
{
strncpy_s((char *)m_items[0].sData, STRSIZE, _T("0"), STRSIZE - 1);
}
DdePostAdvise(m_idDde, m_topic.hsz, m_items[0].hsz);
//软电话状态
//在线
//离线
//ip电话状态
//在线
//离线
}
break;
default:
break;
}
return TRUE;
}
//服务器主动向客户端发送数据
HDDEDATA CWindowMain::DdeAdvreq(UINT wFmt, HSZ topic, HSZ item, HDDEDATA hData){
TCHAR data[STRSIZE];
#ifdef UNICODE
DdeQueryString(m_idDde, item, data, STRSIZE - 1, CP_WINUNICODE);
#else
DdeQueryString(m_idDde, item, data, STRSIZE - 1, CP_WINANSI);
#endif
for (size_t i = 0; i < m_nItemNum; i++){
if(_tcscmp(data, m_items[i].name) == 0){
HDDEDATA ret = DdeCreateDataHandle(m_idDde, m_items[i].sData, STRSIZE, 0, item, wFmt, NULL);
return ret;
}
}
//条目验证失败
return (HDDEDATA)NULL;
}
BOOL CWindowMain::pjInit(){
pj_status_t status;
status = pjsua_create();
if (status != PJ_SUCCESS)
return FALSE;
pjsua_config cfg;
pjsua_logging_config log_cfg;
pjsua_config_default(&cfg);
cfg.cb.on_incoming_call = &on_incoming_call;
cfg.cb.on_call_media_state = &on_call_media_state;
cfg.cb.on_call_state = &on_call_state;
pjsua_logging_config_default(&log_cfg);
log_cfg.console_level = 4;
status = pjsua_init(&cfg, &log_cfg, NULL);
if (status != PJ_SUCCESS)
return FALSE;
pjsua_transport_config cfg1;
pjsua_transport_config_default(&cfg1);
cfg1.port = 5061;
status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg1, NULL);
if (status != PJ_SUCCESS)
return FALSE;
status = pjsua_start();
if (status != PJ_SUCCESS)
return FALSE;
pjsua_acc_config cfg2;
pjsua_acc_config_default(&cfg2);
char ss[STRSIZE]={0};
sprintf(ss, "sip:%s@%s", m_usname, m_serip);
cfg2.id = pj_str(ss);
char ss2[STRSIZE]={0};
sprintf(ss2, "sip:%s", m_serip);
cfg2.reg_uri = pj_str(ss2);
cfg2.cred_count = 1;
cfg2.cred_info[0].realm = pj_str(m_serip);
cfg2.cred_info[0].scheme = pj_str("digest");
cfg2.cred_info[0].username = pj_str(m_usname);
cfg2.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
cfg2.cred_info[0].data = pj_str(m_psd);
status = pjsua_acc_add(&cfg2, PJ_TRUE, &m_acc_id);
if (status != PJ_SUCCESS)
return FALSE;
SetTimer(m_hWnd, TIME_CHECK, TIMESPACE, NULL);
return TRUE;
}
BOOL CWindowMain::pjDestroy(){
pjsua_destroy();
KillTimer(m_hWnd, TIME_CHECK);
return TRUE;
}
void CWindowMain::on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata)
{
pjsua_call_info ci;
PJ_UNUSED_ARG(acc_id);
PJ_UNUSED_ARG(rdata);
pjsua_call_get_info(call_id, &ci);
char str[STRSIZE] = {0};
strcpy(str, ci.remote_info.ptr);
char * pa1 = strchr(str, ':');
char * pa2 = strchr(str, '@');
*pa2 = NULL;
m_this->m_call_id = call_id;
SetTimer(m_this->m_hWnd, TIME_ANSWER, 1000, NULL);
m_this->m_iCall = 0;
}
void CWindowMain::on_call_state(pjsua_call_id call_id, pjsip_event *e)
{
pjsua_call_info ci;
PJ_UNUSED_ARG(e);
pjsua_call_get_info(call_id, &ci);
switch(ci.state){
case PJSIP_INV_STATE_DISCONNECTED:
KillTimer(m_this->m_hWnd, TIME_ANSWER);
pjsua_call_hangup_all();
break;
default:
break;
}
}
void CWindowMain::on_call_media_state(pjsua_call_id call_id)
{
pjsua_call_info ci;
pjsua_call_get_info(call_id, &ci);
if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
pjsua_conf_connect(ci.conf_slot, 0);
pjsua_conf_connect(0, ci.conf_slot);
}
}
3. _tWinMain 程序入口函数
int WINAPI _tWinMain(HINSTANCE hInstance1, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow){
CWindowMain wnd;
wnd.Create();
wnd.Show();
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}