首页 > 代码库 > 使用 pjsip 代码独立开发
使用 pjsip 代码独立开发
1、在不改动pjsip代码的情况下,和pjsip工程目录并行建立win32控制台程序工程P2PTraversal
目录结构如下:
.
├── pjproject-2.6
└── pjsipdemo
2、在VS2008下,新建项目
3、工程引入pjsip的相关配置
本例按照引入pjlib、pjlib-util、pjnath三个模块进行设置,如果引入更多需要参考如下设置增加对应的lib或.h目录设置
① 增加“附加包含目录”(针对引用头文件)
为了减少配置次数,在配置页面选择“所有配置”,可以一次性将“Debug”和“Release”全部配置好。主要是针对头文件,静态库则需要分别设置。
在工程属性页面中,找到C/C++选项的常规配置,在附加包含目录中添加如下路径(相对路径,按照目录层次关系)
../pjproject-2.6/pjlib/include/
../pjproject-2.6/pjlib-util/include/
../pjproject-2.6/pjnath/include/
② 设置“附加库目录”
../pjproject-2.6/pjlib/lib/
../pjproject-2.6/pjlib-util/lib/
../pjproject-2.6/pjnath/lib/
③ 设置“附加依赖项”
Debug设置:
ws2_32.lib
pjlib-i386-Win32-vc8-Debug.lib
pjlib-util-i386-Win32-vc8-Debug.lib
pjnath-i386-Win32-vc8-Debug.lib
Release设置
ws2_32.lib
pjlib-i386-Win32-vc8-Release.lib
pjlib-util-i386-Win32-vc8-Release.lib
pjnath-i386-Win32-vc8-Release.lib
4、在main.cpp文件中加入实现(以改造的icedemo为例)
1 /* $Id: icedemo.c 4624 2013-10-21 06:37:30Z ming $ */ 2 /* 3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 */ 19 #include <stdio.h> 20 #include <stdlib.h> 21 #include <pjlib.h> 22 #include <pjlib-util.h> 23 #include <pjnath.h> 24 25 26 #define THIS_FILE "icedemo.c" 27 28 /* For this demo app, configure longer STUN keep-alive time 29 * so that it does‘t clutter the screen output. 30 */ 31 #define KA_INTERVAL 300 32 33 34 /* This is our global variables */ 35 static struct app_t 36 { 37 /* Command line options are stored here */ 38 struct options 39 { 40 unsigned comp_cnt; 41 pj_str_t ns; 42 int max_host; 43 pj_bool_t regular; 44 pj_str_t stun_srv; 45 pj_str_t turn_srv; 46 pj_bool_t turn_tcp; 47 pj_str_t turn_username; 48 pj_str_t turn_password; 49 pj_bool_t turn_fingerprint; 50 const char *log_file; 51 } opt; 52 53 /* Our global variables */ 54 pj_caching_pool cp; 55 pj_pool_t *pool; 56 pj_thread_t *thread; 57 pj_bool_t thread_quit_flag; 58 pj_ice_strans_cfg ice_cfg; 59 pj_ice_strans *icest; 60 FILE *log_fhnd; 61 62 /* Variables to store parsed remote ICE info */ 63 struct rem_info 64 65 { 66 char ufrag[80]; 67 char pwd[80]; 68 unsigned comp_cnt; 69 pj_sockaddr def_addr[PJ_ICE_MAX_COMP]; 70 unsigned cand_cnt; 71 pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND]; 72 } rem; 73 74 } icedemo; 75 76 /* Utility to display error messages */ 77 static void icedemo_perror(const char *title, pj_status_t status) 78 { 79 char errmsg[PJ_ERR_MSG_SIZE]; 80 81 pj_strerror(status, errmsg, sizeof(errmsg)); 82 PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg)); 83 } 84 85 /* Utility: display error message and exit application (usually 86 * because of fatal error. 87 */ 88 static void err_exit(const char *title, pj_status_t status) 89 { 90 if (status != PJ_SUCCESS) 91 { 92 icedemo_perror(title, status); 93 } 94 PJ_LOG(3,(THIS_FILE, "Shutting down..")); 95 96 if (icedemo.icest) 97 pj_ice_strans_destroy(icedemo.icest); 98 99 pj_thread_sleep(500); 100 101 icedemo.thread_quit_flag = PJ_TRUE; 102 if (icedemo.thread) 103 { 104 pj_thread_join(icedemo.thread); 105 pj_thread_destroy(icedemo.thread); 106 } 107 108 if (icedemo.ice_cfg.stun_cfg.ioqueue) 109 pj_ioqueue_destroy(icedemo.ice_cfg.stun_cfg.ioqueue); 110 111 if (icedemo.ice_cfg.stun_cfg.timer_heap) 112 pj_timer_heap_destroy(icedemo.ice_cfg.stun_cfg.timer_heap); 113 114 pj_caching_pool_destroy(&icedemo.cp); 115 116 pj_shutdown(); 117 118 if (icedemo.log_fhnd) 119 { 120 fclose(icedemo.log_fhnd); 121 icedemo.log_fhnd = NULL; 122 } 123 124 exit(status != PJ_SUCCESS); 125 } 126 127 #define CHECK(expr) status=expr; 128 if (status!=PJ_SUCCESS) { 129 err_exit(#expr, status); 130 } 131 132 /* 133 * This function checks for events from both timer and ioqueue (for 134 * network events). It is invoked by the worker thread. 135 */ 136 static pj_status_t handle_events(unsigned max_msec, unsigned *p_count) 137 { 138 enum { MAX_NET_EVENTS = 1 }; 139 pj_time_val max_timeout = {0, 0}; 140 pj_time_val timeout = { 0, 0}; 141 unsigned count = 0, net_event_count = 0; 142 int c; 143 144 max_timeout.msec = max_msec; 145 146 /* Poll the timer to run it and also to retrieve the earliest entry. */ 147 timeout.sec = timeout.msec = 0; 148 c = pj_timer_heap_poll( icedemo.ice_cfg.stun_cfg.timer_heap, &timeout ); 149 if (c > 0) 150 count += c; 151 152 /* timer_heap_poll should never ever returns negative value, or otherwise 153 * ioqueue_poll() will block forever! 154 */ 155 pj_assert(timeout.sec >= 0 && timeout.msec >= 0); 156 if (timeout.msec >= 1000) timeout.msec = 999; 157 158 /* compare the value with the timeout to wait from timer, and use the 159 * minimum value. 160 */ 161 if (PJ_TIME_VAL_GT(timeout, max_timeout)) 162 timeout = max_timeout; 163 164 /* Poll ioqueue. 165 * Repeat polling the ioqueue while we have immediate events, because 166 * timer heap may process more than one events, so if we only process 167 * one network events at a time (such as when IOCP backend is used), 168 * the ioqueue may have trouble keeping up with the request rate. 169 * 170 * For example, for each send() request, one network event will be 171 * reported by ioqueue for the send() completion. If we don‘t poll 172 * the ioqueue often enough, the send() completion will not be 173 * reported in timely manner. 174 */ 175 do 176 { 177 c = pj_ioqueue_poll( icedemo.ice_cfg.stun_cfg.ioqueue, &timeout); 178 if (c < 0) 179 { 180 pj_status_t err = pj_get_netos_error(); 181 pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout)); 182 if (p_count) 183 *p_count = count; 184 return err; 185 } 186 else if (c == 0) 187 { 188 break; 189 } 190 else 191 { 192 net_event_count += c; 193 timeout.sec = timeout.msec = 0; 194 } 195 } while (c > 0 && net_event_count < MAX_NET_EVENTS); 196 197 count += net_event_count; 198 if (p_count) 199 *p_count = count; 200 201 return PJ_SUCCESS; 202 203 } 204 205 /* 206 * This is the worker thread that polls event in the background. 207 */ 208 static int icedemo_worker_thread(void *unused) 209 { 210 PJ_UNUSED_ARG(unused); 211 212 while (!icedemo.thread_quit_flag) 213 { 214 handle_events(500, NULL); 215 } 216 217 return 0; 218 } 219 220 /* 221 * This is the callback that is registered to the ICE stream transport to 222 * receive notification about incoming data. By "data" it means application 223 * data such as RTP/RTCP, and not packets that belong to ICE signaling (such 224 * as STUN connectivity checks or TURN signaling). 225 */ 226 static void cb_on_rx_data(pj_ice_strans *ice_st, 227 unsigned comp_id, 228 void *pkt, pj_size_t size, 229 const pj_sockaddr_t *src_addr, 230 unsigned src_addr_len) 231 { 232 char ipstr[PJ_INET6_ADDRSTRLEN+10]; 233 234 PJ_UNUSED_ARG(ice_st); 235 PJ_UNUSED_ARG(src_addr_len); 236 PJ_UNUSED_ARG(pkt); 237 238 // Don‘t do this! It will ruin the packet buffer in case TCP is used! 239 //((char*)pkt)[size] = ‘\0‘; 240 241 PJ_LOG(3,(THIS_FILE, "Component %d: received %d bytes data from %s: \"%.*s\"", 242 comp_id, size, 243 pj_sockaddr_print(src_addr, ipstr, sizeof(ipstr), 3), 244 (unsigned)size, 245 (char*)pkt)); 246 } 247 248 /* 249 * This is the callback that is registered to the ICE stream transport to 250 * receive notification about ICE state progression. 251 */ 252 static void cb_on_ice_complete(pj_ice_strans *ice_st, 253 pj_ice_strans_op op, 254 pj_status_t status) 255 { 256 const char *opname = 257 (op==PJ_ICE_STRANS_OP_INIT? "initialization" : 258 (op==PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op")); 259 260 if (status == PJ_SUCCESS) 261 { 262 PJ_LOG(3,(THIS_FILE, "ICE %s successful", opname)); 263 } 264 else 265 { 266 char errmsg[PJ_ERR_MSG_SIZE]; 267 268 pj_strerror(status, errmsg, sizeof(errmsg)); 269 PJ_LOG(1,(THIS_FILE, "ICE %s failed: %s", opname, errmsg)); 270 pj_ice_strans_destroy(ice_st); 271 icedemo.icest = NULL; 272 } 273 } 274 275 /* log callback to write to file */ 276 static void log_func(int level, const char *data, int len) 277 { 278 pj_log_write(level, data, len); 279 if (icedemo.log_fhnd) 280 { 281 if (fwrite(data, len, 1, icedemo.log_fhnd) != 1) 282 return; 283 } 284 } 285 286 /* 287 * This is the main application initialization function. It is called 288 * once (and only once) during application initialization sequence by 289 * main(). 290 */ 291 static pj_status_t icedemo_init(void) 292 { 293 pj_status_t status; 294 295 296 //设置日志文件路径 297 icedemo.opt.log_file = pj_str(".\\Demo.log").ptr; 298 pj_log_set_level(6); 299 300 if (icedemo.opt.log_file) 301 { 302 icedemo.log_fhnd = fopen(icedemo.opt.log_file, "a"); 303 pj_log_set_log_func(&log_func); 304 } 305 306 /* Initialize the libraries before anything else */ 307 CHECK( pj_init() ); 308 CHECK( pjlib_util_init() ); 309 CHECK( pjnath_init() ); 310 311 /* Must create pool factory, where memory allocations come from */ 312 pj_caching_pool_init(&icedemo.cp, NULL, 0); 313 314 /* Init our ICE settings with null values */ 315 pj_ice_strans_cfg_default(&icedemo.ice_cfg); 316 317 icedemo.ice_cfg.stun_cfg.pf = &icedemo.cp.factory; 318 319 /* Create application memory pool */ 320 icedemo.pool = pj_pool_create(&icedemo.cp.factory, "icedemo", 321 512, 512, NULL); 322 323 /* Create timer heap for timer stuff */ 324 CHECK( pj_timer_heap_create(icedemo.pool, 100, 325 &icedemo.ice_cfg.stun_cfg.timer_heap) ); 326 327 /* and create ioqueue for network I/O stuff */ 328 CHECK( pj_ioqueue_create(icedemo.pool, 16, 329 &icedemo.ice_cfg.stun_cfg.ioqueue) ); 330 331 /* something must poll the timer heap and ioqueue, 332 * unless we‘re on Symbian where the timer heap and ioqueue run 333 * on themselves. 334 */ 335 CHECK( pj_thread_create(icedemo.pool, "icedemo", &icedemo_worker_thread, 336 NULL, 0, 0, &icedemo.thread) ); 337 338 icedemo.ice_cfg.af = pj_AF_INET(); 339 340 /* Create DNS resolver if nameserver is set */ 341 if (icedemo.opt.ns.slen) 342 { 343 CHECK( pj_dns_resolver_create(&icedemo.cp.factory, 344 "resolver", 345 0, 346 icedemo.ice_cfg.stun_cfg.timer_heap, 347 icedemo.ice_cfg.stun_cfg.ioqueue, 348 &icedemo.ice_cfg.resolver) ); 349 350 CHECK( pj_dns_resolver_set_ns(icedemo.ice_cfg.resolver, 1, 351 &icedemo.opt.ns, NULL) ); 352 } 353 354 /* -= Start initializing ICE stream transport config =- */ 355 356 /* Maximum number of host candidates */ 357 if (icedemo.opt.max_host != -1) 358 icedemo.ice_cfg.stun.max_host_cands = icedemo.opt.max_host; 359 360 /* Nomination strategy */ 361 if (icedemo.opt.regular) 362 icedemo.ice_cfg.opt.aggressive = PJ_FALSE; 363 else 364 icedemo.ice_cfg.opt.aggressive = PJ_TRUE; 365 366 /* 手动设置TURN服务信息 */ 367 icedemo.opt.turn_srv = pj_str("11.11.11.11:8888"); 368 icedemo.opt.stun_srv = icedemo.opt.turn_srv; 369 icedemo.opt.turn_username = pj_str("test"); 370 icedemo.opt.turn_password = pj_str("test"); 371 372 /* Configure STUN/srflx candidate resolution */ 373 if (icedemo.opt.stun_srv.slen) 374 { 375 char *pos; 376 377 /* Command line option may contain port number */ 378 if ((pos=pj_strchr(&icedemo.opt.stun_srv, ‘:‘)) != NULL) 379 { 380 icedemo.ice_cfg.stun.server.ptr = icedemo.opt.stun_srv.ptr; 381 icedemo.ice_cfg.stun.server.slen = (pos - icedemo.opt.stun_srv.ptr); 382 383 icedemo.ice_cfg.stun.port = (pj_uint16_t)atoi(pos+1); 384 } 385 else 386 { 387 icedemo.ice_cfg.stun.server = icedemo.opt.stun_srv; 388 icedemo.ice_cfg.stun.port = PJ_STUN_PORT; 389 } 390 391 /* For this demo app, configure longer STUN keep-alive time 392 * so that it does‘t clutter the screen output. 393 */ 394 icedemo.ice_cfg.stun.cfg.ka_interval = KA_INTERVAL; 395 } 396 397 /* Configure TURN candidate */ 398 if (icedemo.opt.turn_srv.slen) 399 { 400 char *pos; 401 402 /* Command line option may contain port number */ 403 if ((pos=pj_strchr(&icedemo.opt.turn_srv, ‘:‘)) != NULL) 404 { 405 icedemo.ice_cfg.turn.server.ptr = icedemo.opt.turn_srv.ptr; 406 icedemo.ice_cfg.turn.server.slen = (pos - icedemo.opt.turn_srv.ptr); 407 408 icedemo.ice_cfg.turn.port = (pj_uint16_t)atoi(pos+1); 409 } 410 else 411 { 412 icedemo.ice_cfg.turn.server = icedemo.opt.turn_srv; 413 icedemo.ice_cfg.turn.port = PJ_STUN_PORT; 414 } 415 416 /* TURN credential */ 417 icedemo.ice_cfg.turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC; 418 icedemo.ice_cfg.turn.auth_cred.data.static_cred.username = icedemo.opt.turn_username; 419 icedemo.ice_cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN; 420 icedemo.ice_cfg.turn.auth_cred.data.static_cred.data =http://www.mamicode.com/ icedemo.opt.turn_password; 421 422 /* Connection type to TURN server */ 423 //使用TCP协议 424 icedemo.opt.turn_tcp = PJ_TRUE; 425 if (icedemo.opt.turn_tcp) 426 icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_TCP; 427 else 428 icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_UDP; 429 430 /* For this demo app, configure longer keep-alive time 431 * so that it does‘t clutter the screen output. 432 */ 433 icedemo.ice_cfg.turn.alloc_param.ka_interval = KA_INTERVAL; 434 } 435 436 /* -= That‘s it for now, initialization is complete =- */ 437 return PJ_SUCCESS; 438 } 439 440 441 /* 442 * Create ICE stream transport instance, invoked from the menu. 443 */ 444 static void icedemo_create_instance(void) 445 { 446 pj_ice_strans_cb icecb; 447 pj_status_t status; 448 449 if (icedemo.icest != NULL) 450 { 451 puts("ICE instance already created, destroy it first"); 452 return; 453 } 454 455 /* init the callback */ 456 pj_bzero(&icecb, sizeof(icecb)); 457 icecb.on_rx_data =http://www.mamicode.com/ cb_on_rx_data; 458 icecb.on_ice_complete = cb_on_ice_complete; 459 460 /* create the instance */ 461 status = pj_ice_strans_create("icedemo", /* object name */ 462 &icedemo.ice_cfg, /* settings */ 463 icedemo.opt.comp_cnt, /* comp_cnt */ 464 NULL, /* user data */ 465 &icecb, /* callback */ 466 &icedemo.icest) /* instance ptr */ 467 ; 468 if (status != PJ_SUCCESS) 469 icedemo_perror("error creating ice", status); 470 else 471 PJ_LOG(3,(THIS_FILE, "ICE instance successfully created")); 472 } 473 474 /* Utility to nullify parsed remote info */ 475 static void reset_rem_info(void) 476 { 477 pj_bzero(&icedemo.rem, sizeof(icedemo.rem)); 478 } 479 480 481 /* 482 * Destroy ICE stream transport instance, invoked from the menu. 483 */ 484 static void icedemo_destroy_instance(void) 485 { 486 if (icedemo.icest == NULL) 487 { 488 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first")); 489 return; 490 } 491 492 pj_ice_strans_destroy(icedemo.icest); 493 icedemo.icest = NULL; 494 495 reset_rem_info(); 496 497 PJ_LOG(3,(THIS_FILE, "ICE instance destroyed")); 498 } 499 500 501 /* 502 * Create ICE session, invoked from the menu. 503 */ 504 static void icedemo_init_session(unsigned rolechar) 505 { 506 pj_ice_sess_role role = (pj_tolower((pj_uint8_t)rolechar)==‘o‘ ? 507 PJ_ICE_SESS_ROLE_CONTROLLING : 508 PJ_ICE_SESS_ROLE_CONTROLLED); 509 pj_status_t status; 510 511 if (icedemo.icest == NULL) 512 { 513 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first")); 514 return; 515 } 516 517 if (pj_ice_strans_has_sess(icedemo.icest)) 518 { 519 PJ_LOG(1,(THIS_FILE, "Error: Session already created")); 520 return; 521 } 522 523 status = pj_ice_strans_init_ice(icedemo.icest, role, NULL, NULL); 524 if (status != PJ_SUCCESS) 525 icedemo_perror("error creating session", status); 526 else 527 PJ_LOG(3,(THIS_FILE, "ICE session created")); 528 529 reset_rem_info(); 530 } 531 532 533 /* 534 * Stop/destroy ICE session, invoked from the menu. 535 */ 536 static void icedemo_stop_session(void) 537 { 538 pj_status_t status; 539 540 if (icedemo.icest == NULL) 541 { 542 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first")); 543 return; 544 } 545 546 if (!pj_ice_strans_has_sess(icedemo.icest)) 547 { 548 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first")); 549 return; 550 } 551 552 status = pj_ice_strans_stop_ice(icedemo.icest); 553 if (status != PJ_SUCCESS) 554 icedemo_perror("error stopping session", status); 555 else 556 PJ_LOG(3,(THIS_FILE, "ICE session stopped")); 557 558 reset_rem_info(); 559 } 560 561 #define PRINT(...) 562 printed = pj_ansi_snprintf(p, maxlen - (p-buffer), 563 __VA_ARGS__); 564 if (printed <= 0 || printed >= (int)(maxlen - (p-buffer))) 565 return -PJ_ETOOSMALL; 566 p += printed 567 568 569 /* Utility to create a=candidate SDP attribute */ 570 static int print_cand(char buffer[], unsigned maxlen, 571 const pj_ice_sess_cand *cand) 572 { 573 char ipaddr[PJ_INET6_ADDRSTRLEN]; 574 char *p = buffer; 575 int printed; 576 577 PRINT("a=candidate:%.*s %u UDP %u %s %u typ ", 578 (int)cand->foundation.slen, 579 cand->foundation.ptr, 580 (unsigned)cand->comp_id, 581 cand->prio, 582 pj_sockaddr_print(&cand->addr, ipaddr, 583 sizeof(ipaddr), 0), 584 (unsigned)pj_sockaddr_get_port(&cand->addr)); 585 586 PRINT("%s\n", 587 pj_ice_get_cand_type_name(cand->type)); 588 589 if (p == buffer+maxlen) 590 return -PJ_ETOOSMALL; 591 592 *p = ‘\0‘; 593 594 return (int)(p-buffer); 595 } 596 597 /* 598 * Encode ICE information in SDP. 599 */ 600 static int encode_session(char buffer[], unsigned maxlen) 601 { 602 char *p = buffer; 603 unsigned comp; 604 int printed; 605 pj_str_t local_ufrag, local_pwd; 606 pj_status_t status; 607 608 /* Write "dummy" SDP v=, o=, s=, and t= lines */ 609 PRINT("v=0\no=- 3414953978 3414953978 IN IP4 localhost\ns=ice\nt=0 0\n"); 610 611 /* Get ufrag and pwd from current session */ 612 pj_ice_strans_get_ufrag_pwd(icedemo.icest, &local_ufrag, &local_pwd, 613 NULL, NULL); 614 615 /* Write the a=ice-ufrag and a=ice-pwd attributes */ 616 PRINT("a=ice-ufrag:%.*s\na=ice-pwd:%.*s\n", 617 (int)local_ufrag.slen, 618 local_ufrag.ptr, 619 (int)local_pwd.slen, 620 local_pwd.ptr); 621 622 /* Write each component */ 623 for (comp=0; comp<icedemo.opt.comp_cnt; ++comp) 624 { 625 unsigned j, cand_cnt; 626 pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND]; 627 char ipaddr[PJ_INET6_ADDRSTRLEN]; 628 629 /* Get default candidate for the component */ 630 status = pj_ice_strans_get_def_cand(icedemo.icest, comp+1, &cand[0]); 631 if (status != PJ_SUCCESS) 632 return -status; 633 634 /* Write the default address */ 635 if (comp==0) 636 { 637 /* For component 1, default address is in m= and c= lines */ 638 PRINT("m=audio %d RTP/AVP 0\n" 639 "c=IN IP4 %s\n", 640 (int)pj_sockaddr_get_port(&cand[0].addr), 641 pj_sockaddr_print(&cand[0].addr, ipaddr, 642 sizeof(ipaddr), 0)); 643 } 644 else if (comp==1) 645 { 646 /* For component 2, default address is in a=rtcp line */ 647 PRINT("a=rtcp:%d IN IP4 %s\n", 648 (int)pj_sockaddr_get_port(&cand[0].addr), 649 pj_sockaddr_print(&cand[0].addr, ipaddr, 650 sizeof(ipaddr), 0)); 651 } 652 else 653 { 654 /* For other components, we‘ll just invent this.. */ 655 PRINT("a=Xice-defcand:%d IN IP4 %s\n", 656 (int)pj_sockaddr_get_port(&cand[0].addr), 657 pj_sockaddr_print(&cand[0].addr, ipaddr, 658 sizeof(ipaddr), 0)); 659 } 660 661 /* Enumerate all candidates for this component */ 662 cand_cnt = PJ_ARRAY_SIZE(cand); 663 status = pj_ice_strans_enum_cands(icedemo.icest, comp+1, 664 &cand_cnt, cand); 665 if (status != PJ_SUCCESS) 666 return -status; 667 668 /* And encode the candidates as SDP */ 669 for (j=0; j<cand_cnt; ++j) 670 { 671 printed = print_cand(p, maxlen - (unsigned)(p-buffer), &cand[j]); 672 if (printed < 0) 673 return -PJ_ETOOSMALL; 674 p += printed; 675 } 676 } 677 678 if (p == buffer+maxlen) 679 return -PJ_ETOOSMALL; 680 681 *p = ‘\0‘; 682 return (int)(p - buffer); 683 } 684 685 686 /* 687 * Show information contained in the ICE stream transport. This is 688 * invoked from the menu. 689 */ 690 static void icedemo_show_ice(void) 691 { 692 static char buffer[1000]; 693 int len; 694 695 if (icedemo.icest == NULL) 696 { 697 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first")); 698 return; 699 } 700 701 puts("General info"); 702 puts("---------------"); 703 printf("Component count : %d\n", icedemo.opt.comp_cnt); 704 printf("Status : "); 705 if (pj_ice_strans_sess_is_complete(icedemo.icest)) 706 puts("negotiation complete"); 707 else if (pj_ice_strans_sess_is_running(icedemo.icest)) 708 puts("negotiation is in progress"); 709 else if (pj_ice_strans_has_sess(icedemo.icest)) 710 puts("session ready"); 711 else 712 puts("session not created"); 713 714 if (!pj_ice_strans_has_sess(icedemo.icest)) 715 { 716 puts("Create the session first to see more info"); 717 return; 718 } 719 720 printf("Negotiated comp_cnt: %d\n", 721 pj_ice_strans_get_running_comp_cnt(icedemo.icest)); 722 printf("Role : %s\n", 723 pj_ice_strans_get_role(icedemo.icest)==PJ_ICE_SESS_ROLE_CONTROLLED ? 724 "controlled" : "controlling"); 725 726 len = encode_session(buffer, sizeof(buffer)); 727 if (len < 0) 728 err_exit("not enough buffer to show ICE status", -len); 729 730 puts(""); 731 printf("Local SDP (paste this to remote host):\n" 732 "--------------------------------------\n" 733 "%s\n", buffer); 734 735 736 puts(""); 737 puts("Remote info:\n" 738 "----------------------"); 739 if (icedemo.rem.cand_cnt==0) 740 { 741 puts("No remote info yet"); 742 } 743 else 744 { 745 unsigned i; 746 747 printf("Remote ufrag : %s\n", icedemo.rem.ufrag); 748 printf("Remote password : %s\n", icedemo.rem.pwd); 749 printf("Remote cand. cnt. : %d\n", icedemo.rem.cand_cnt); 750 751 for (i=0; i<icedemo.rem.cand_cnt; ++i) 752 { 753 len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]); 754 if (len < 0) 755 err_exit("not enough buffer to show ICE status", -len); 756 757 printf(" %s", buffer); 758 } 759 } 760 } 761 762 763 /* 764 * Input and parse SDP from the remote (containing remote‘s ICE information) 765 * and save it to global variables. 766 */ 767 static void icedemo_input_remote(void) 768 { 769 char linebuf[80]; 770 unsigned media_cnt = 0; 771 unsigned comp0_port = 0; 772 char comp0_addr[80]; 773 pj_bool_t done = PJ_FALSE; 774 775 puts("Paste SDP from remote host, end with empty line"); 776 777 reset_rem_info(); 778 779 comp0_addr[0] = ‘\0‘; 780 781 while (!done) 782 { 783 pj_size_t len; 784 char *line; 785 786 printf(">"); 787 if (stdout) fflush(stdout); 788 789 if (fgets(linebuf, sizeof(linebuf), stdin)==NULL) 790 break; 791 792 len = strlen(linebuf); 793 while (len && (linebuf[len-1] == ‘\r‘ || linebuf[len-1] == ‘\n‘)) 794 linebuf[--len] = ‘\0‘; 795 796 line = linebuf; 797 while (len && pj_isspace(*line)) 798 ++line, --len; 799 800 if (len==0) 801 break; 802 803 /* Ignore subsequent media descriptors */ 804 if (media_cnt > 1) 805 continue; 806 807 switch (line[0]) 808 { 809 case ‘m‘: 810 811 { 812 int cnt; 813 char media[32], portstr[32]; 814 815 ++media_cnt; 816 if (media_cnt > 1) 817 { 818 puts("Media line ignored"); 819 break; 820 } 821 822 cnt = sscanf(line+2, "%s %s RTP/", media, portstr); 823 if (cnt != 2) 824 { 825 PJ_LOG(1,(THIS_FILE, "Error parsing media line")); 826 goto on_error; 827 } 828 829 comp0_port = atoi(portstr); 830 831 } 832 break; 833 case ‘c‘: 834 835 { 836 int cnt; 837 char c[32], net[32], ip[80]; 838 839 cnt = sscanf(line+2, "%s %s %s", c, net, ip); 840 if (cnt != 3) 841 { 842 PJ_LOG(1,(THIS_FILE, "Error parsing connection line")); 843 goto on_error; 844 } 845 846 strcpy(comp0_addr, ip); 847 } 848 break; 849 case ‘a‘: 850 851 { 852 char *attr = strtok(line+2, ": \t\r\n"); 853 if (strcmp(attr, "ice-ufrag")==0) 854 { 855 strcpy(icedemo.rem.ufrag, attr+strlen(attr)+1); 856 } 857 else if (strcmp(attr, "ice-pwd")==0) 858 { 859 strcpy(icedemo.rem.pwd, attr+strlen(attr)+1); 860 } 861 else if (strcmp(attr, "rtcp")==0) 862 { 863 char *val = attr+strlen(attr)+1; 864 int af, cnt; 865 int port; 866 char net[32], ip[64]; 867 pj_str_t tmp_addr; 868 pj_status_t status; 869 870 cnt = sscanf(val, "%d IN %s %s", &port, net, ip); 871 if (cnt != 3) 872 { 873 PJ_LOG(1,(THIS_FILE, "Error parsing rtcp attribute")); 874 goto on_error; 875 } 876 877 if (strchr(ip, ‘:‘)) 878 af = pj_AF_INET6(); 879 else 880 af = pj_AF_INET(); 881 882 pj_sockaddr_init(af, &icedemo.rem.def_addr[1], NULL, 0); 883 tmp_addr = pj_str(ip); 884 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[1], 885 &tmp_addr); 886 if (status != PJ_SUCCESS) 887 { 888 PJ_LOG(1,(THIS_FILE, "Invalid IP address")); 889 goto on_error; 890 } 891 pj_sockaddr_set_port(&icedemo.rem.def_addr[1], (pj_uint16_t)port); 892 893 } 894 else if (strcmp(attr, "candidate")==0) 895 { 896 char *sdpcand = attr+strlen(attr)+1; 897 int af, cnt; 898 char foundation[32], transport[12], ipaddr[80], type[32]; 899 pj_str_t tmpaddr; 900 int comp_id, prio, port; 901 pj_ice_sess_cand *cand; 902 pj_status_t status; 903 904 cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s", 905 foundation, 906 &comp_id, 907 transport, 908 &prio, 909 ipaddr, 910 &port, 911 type); 912 if (cnt != 7) 913 { 914 PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line")); 915 goto on_error; 916 } 917 918 cand = &icedemo.rem.cand[icedemo.rem.cand_cnt]; 919 pj_bzero(cand, sizeof(*cand)); 920 921 if (strcmp(type, "host")==0) 922 cand->type = PJ_ICE_CAND_TYPE_HOST; 923 else if (strcmp(type, "srflx")==0) 924 cand->type = PJ_ICE_CAND_TYPE_SRFLX; 925 else if (strcmp(type, "relay")==0) 926 cand->type = PJ_ICE_CAND_TYPE_RELAYED; 927 else 928 { 929 PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type ‘%s‘", 930 type)); 931 goto on_error; 932 } 933 934 cand->comp_id = (pj_uint8_t)comp_id; 935 pj_strdup2(icedemo.pool, &cand->foundation, foundation); 936 cand->prio = prio; 937 938 if (strchr(ipaddr, ‘:‘)) 939 af = pj_AF_INET6(); 940 else 941 af = pj_AF_INET(); 942 943 tmpaddr = pj_str(ipaddr); 944 pj_sockaddr_init(af, &cand->addr, NULL, 0); 945 status = pj_sockaddr_set_str_addr(af, &cand->addr, &tmpaddr); 946 if (status != PJ_SUCCESS) 947 { 948 PJ_LOG(1,(THIS_FILE, "Error: invalid IP address ‘%s‘", 949 ipaddr)); 950 goto on_error; 951 } 952 953 pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)port); 954 955 ++icedemo.rem.cand_cnt; 956 957 if (cand->comp_id > icedemo.rem.comp_cnt) 958 icedemo.rem.comp_cnt = cand->comp_id; 959 } 960 } 961 break; 962 } 963 } 964 965 if (icedemo.rem.cand_cnt==0 || 966 icedemo.rem.ufrag[0]==0 || 967 icedemo.rem.pwd[0]==0 || 968 icedemo.rem.comp_cnt == 0) 969 { 970 PJ_LOG(1, (THIS_FILE, "Error: not enough info")); 971 goto on_error; 972 } 973 974 if (comp0_port==0 || comp0_addr[0]==‘\0‘) 975 { 976 PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found")); 977 goto on_error; 978 } 979 else 980 { 981 int af; 982 pj_str_t tmp_addr; 983 pj_status_t status; 984 985 if (strchr(comp0_addr, ‘:‘)) 986 af = pj_AF_INET6(); 987 else 988 af = pj_AF_INET(); 989 990 pj_sockaddr_init(af, &icedemo.rem.def_addr[0], NULL, 0); 991 tmp_addr = pj_str(comp0_addr); 992 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[0], 993 &tmp_addr); 994 if (status != PJ_SUCCESS) 995 { 996 PJ_LOG(1,(THIS_FILE, "Invalid IP address in c= line")); 997 goto on_error; 998 } 999 pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port); 1000 } 1001 1002 PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added", 1003 icedemo.rem.cand_cnt)); 1004 return; 1005 1006 on_error: 1007 reset_rem_info(); 1008 } 1009 1010 1011 /* 1012 * Start ICE negotiation! This function is invoked from the menu. 1013 */ 1014 static void icedemo_start_nego(void) 1015 { 1016 pj_str_t rufrag, rpwd; 1017 pj_status_t status; 1018 1019 if (icedemo.icest == NULL) 1020 { 1021 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first")); 1022 return; 1023 } 1024 1025 if (!pj_ice_strans_has_sess(icedemo.icest)) 1026 { 1027 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first")); 1028 return; 1029 } 1030 1031 if (icedemo.rem.cand_cnt == 0) 1032 { 1033 PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first")); 1034 return; 1035 } 1036 1037 PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation..")); 1038 1039 status = pj_ice_strans_start_ice(icedemo.icest, 1040 pj_cstr(&rufrag, icedemo.rem.ufrag), 1041 pj_cstr(&rpwd, icedemo.rem.pwd), 1042 icedemo.rem.cand_cnt, 1043 icedemo.rem.cand); 1044 if (status != PJ_SUCCESS) 1045 icedemo_perror("Error starting ICE", status); 1046 else 1047 PJ_LOG(3,(THIS_FILE, "ICE negotiation started")); 1048 } 1049 1050 1051 /* 1052 * Send application data to remote agent. 1053 */ 1054 static void icedemo_send_data(unsigned comp_id, const char *data) 1055 { 1056 pj_status_t status; 1057 1058 if (icedemo.icest == NULL) 1059 { 1060 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first")); 1061 return; 1062 } 1063 1064 if (!pj_ice_strans_has_sess(icedemo.icest)) 1065 { 1066 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first")); 1067 return; 1068 } 1069 1070 /* 1071 if (!pj_ice_strans_sess_is_complete(icedemo.icest)) 1072 { 1073 PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress")); 1074 return; 1075 } 1076 */ 1077 1078 if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icedemo.icest)) 1079 { 1080 PJ_LOG(1,(THIS_FILE, "Error: invalid component ID")); 1081 return; 1082 } 1083 1084 status = pj_ice_strans_sendto(icedemo.icest, comp_id, data, strlen(data), 1085 &icedemo.rem.def_addr[comp_id-1], 1086 pj_sockaddr_get_len(&icedemo.rem.def_addr[comp_id-1])); 1087 if (status != PJ_SUCCESS) 1088 icedemo_perror("Error sending data", status); 1089 else 1090 PJ_LOG(3,(THIS_FILE, "Data sent")); 1091 } 1092 1093 1094 /* 1095 * Display help for the menu. 1096 */ 1097 static void icedemo_help_menu(void) 1098 { 1099 puts(""); 1100 puts("-= Help on using ICE and this icedemo program =-"); 1101 puts(""); 1102 puts("This application demonstrates how to use ICE in pjnath without having\n" 1103 "to use the SIP protocol. To use this application, you will need to run\n" 1104 "two instances of this application, to simulate two ICE agents.\n"); 1105 1106 puts("Basic ICE flow:\n" 1107 " create instance [menu \"c\"]\n" 1108 " repeat these steps as wanted:\n" 1109 " - init session as offerer or answerer [menu \"i\"]\n" 1110 " - display our SDP [menu \"s\"]\n" 1111 " - \"send\" our SDP from the \"show\" output above to remote, by\n" 1112 " copy-pasting the SDP to the other icedemo application\n" 1113 " - parse remote SDP, by pasting SDP generated by the other icedemo\n" 1114 " instance [menu \"r\"]\n" 1115 " - begin ICE negotiation in our end [menu \"b\"], and \n" 1116 " - immediately begin ICE negotiation in the other icedemo instance\n" 1117 " - ICE negotiation will run, and result will be printed to screen\n" 1118 " - send application data to remote [menu \"x\"]\n" 1119 " - end/stop ICE session [menu \"e\"]\n" 1120 " destroy instance [menu \"d\"]\n" 1121 ""); 1122 1123 puts(""); 1124 puts("This concludes the help screen."); 1125 puts(""); 1126 } 1127 1128 1129 /* 1130 * Display console menu 1131 */ 1132 static void icedemo_print_menu(void) 1133 { 1134 puts(""); 1135 puts("+----------------------------------------------------------------------+"); 1136 puts("| M E N U |"); 1137 puts("+---+------------------------------------------------------------------+"); 1138 puts("| c | create Create the instance |"); 1139 puts("| d | destroy Destroy the instance |"); 1140 puts("| i | init o|a Initialize ICE session as offerer or answerer |"); 1141 puts("| e | stop End/stop ICE session |"); 1142 puts("| s | show Display local ICE info |"); 1143 puts("| r | remote Input remote ICE info |"); 1144 puts("| b | start Begin ICE negotiation |"); 1145 puts("| x | send <compid> .. Send data to remote |"); 1146 puts("+---+------------------------------------------------------------------+"); 1147 puts("| h | help * Help! * |"); 1148 puts("| q | quit Quit |"); 1149 puts("+----------------------------------------------------------------------+"); 1150 } 1151 1152 1153 /* 1154 * Main console loop. 1155 */ 1156 static void icedemo_console(void) 1157 { 1158 pj_bool_t app_quit = PJ_FALSE; 1159 1160 while (!app_quit) 1161 { 1162 char input[80], *cmd; 1163 const char *SEP = " \t\r\n"; 1164 pj_size_t len; 1165 1166 icedemo_print_menu(); 1167 1168 printf("Input: "); 1169 if (stdout) fflush(stdout); 1170 1171 pj_bzero(input, sizeof(input)); 1172 if (fgets(input, sizeof(input), stdin) == NULL) 1173 break; 1174 1175 len = strlen(input); 1176 while (len && (input[len-1]==‘\r‘ || input[len-1]==‘\n‘)) 1177 input[--len] = ‘\0‘; 1178 1179 cmd = strtok(input, SEP); 1180 if (!cmd) 1181 continue; 1182 1183 if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) 1184 { 1185 icedemo_create_instance(); 1186 } 1187 else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) 1188 { 1189 icedemo_destroy_instance(); 1190 } 1191 else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) 1192 { 1193 char *role = strtok(NULL, SEP); 1194 if (role) 1195 icedemo_init_session(*role); 1196 else 1197 puts("error: Role required"); 1198 } 1199 else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) 1200 { 1201 icedemo_stop_session(); 1202 } 1203 else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) 1204 { 1205 icedemo_show_ice(); 1206 } 1207 else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) 1208 { 1209 icedemo_input_remote(); 1210 } 1211 else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) 1212 { 1213 icedemo_start_nego(); 1214 } 1215 else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) 1216 { 1217 char *comp = strtok(NULL, SEP); 1218 1219 if (!comp) 1220 { 1221 PJ_LOG(1,(THIS_FILE, "Error: component ID required")); 1222 } 1223 else 1224 { 1225 char *data = http://www.mamicode.com/comp + strlen(comp) + 1; 1226 if (!data) 1227 data = http://www.mamicode.com/""; 1228 icedemo_send_data(atoi(comp), data); 1229 } 1230 1231 } 1232 else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) 1233 { 1234 icedemo_help_menu(); 1235 } 1236 else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) 1237 { 1238 app_quit = PJ_TRUE; 1239 } 1240 else 1241 { 1242 printf("Invalid command ‘%s‘\n", cmd); 1243 1244 } 1245 } 1246 } 1247 1248 1249 /* 1250 * Display program usage. 1251 */ 1252 static void icedemo_usage() 1253 { 1254 puts("Usage: icedemo [optons]"); 1255 printf("icedemo v%s by pjsip.org\n", pj_get_version()); 1256 puts(""); 1257 puts("General options:"); 1258 puts(" --comp-cnt, -c N Component count (default=1)"); 1259 puts(" --nameserver, -n IP Configure nameserver to activate DNS SRV"); 1260 puts(" resolution"); 1261 puts(" --max-host, -H N Set max number of host candidates to N"); 1262 puts(" --regular, -R Use regular nomination (default aggressive)"); 1263 puts(" --log-file, -L FILE Save output to log FILE"); 1264 puts(" --help, -h Display this screen."); 1265 puts(""); 1266 puts("STUN related options:"); 1267 puts(" --stun-srv, -s HOSTDOM Enable srflx candidate by resolving to STUN server."); 1268 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain"); 1269 puts(" name if DNS SRV resolution is used."); 1270 puts(""); 1271 puts("TURN related options:"); 1272 puts(" --turn-srv, -t HOSTDOM Enable relayed candidate by using this TURN server."); 1273 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain"); 1274 puts(" name if DNS SRV resolution is used."); 1275 puts(" --turn-tcp, -T Use TCP to connect to TURN server"); 1276 puts(" --turn-username, -u UID Set TURN username of the credential to UID"); 1277 puts(" --turn-password, -p PWD Set password of the credential to WPWD"); 1278 puts(" --turn-fingerprint, -F Use fingerprint for outgoing TURN requests"); 1279 puts(""); 1280 } 1281 1282 1283 /* 1284 * And here‘s the main() 1285 */ 1286 int main(int argc, char *argv[]) 1287 { 1288 1289 struct pj_getopt_option long_options[] = 1290 { 1291 { "comp-cnt", 1, 0, ‘c‘}, 1292 { "nameserver", 1, 0, ‘n‘}, 1293 { "max-host", 1, 0, ‘H‘}, 1294 { "help", 0, 0, ‘h‘}, 1295 { "stun-srv", 1, 0, ‘s‘}, 1296 { "turn-srv", 1, 0, ‘t‘}, 1297 { "turn-tcp", 0, 0, ‘T‘}, 1298 { "turn-username", 1, 0, ‘u‘}, 1299 { "turn-password", 1, 0, ‘p‘}, 1300 { "turn-fingerprint", 0, 0, ‘F‘}, 1301 { "regular", 0, 0, ‘R‘}, 1302 { "log-file", 1, 0, ‘L‘}, 1303 }; 1304 int c, opt_id; 1305 pj_status_t status; 1306 1307 icedemo.opt.comp_cnt = 1; 1308 icedemo.opt.max_host = -1; 1309 1310 while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:L:hTFR", long_options, &opt_id))!=-1) 1311 { 1312 switch (c) 1313 { 1314 case ‘c‘: 1315 icedemo.opt.comp_cnt = atoi(pj_optarg); 1316 if (icedemo.opt.comp_cnt < 1 || icedemo.opt.comp_cnt >= PJ_ICE_MAX_COMP) 1317 { 1318 puts("Invalid component count value"); 1319 return 1; 1320 } 1321 break; 1322 case ‘n‘: 1323 icedemo.opt.ns = pj_str(pj_optarg); 1324 break; 1325 case ‘H‘: 1326 icedemo.opt.max_host = atoi(pj_optarg); 1327 break; 1328 case ‘h‘: 1329 icedemo_usage(); 1330 return 0; 1331 case ‘s‘: 1332 icedemo.opt.stun_srv = pj_str(pj_optarg); 1333 break; 1334 case ‘t‘: 1335 icedemo.opt.turn_srv = pj_str(pj_optarg); 1336 break; 1337 case ‘T‘: 1338 icedemo.opt.turn_tcp = PJ_TRUE; 1339 break; 1340 case ‘u‘: 1341 icedemo.opt.turn_username = pj_str(pj_optarg); 1342 break; 1343 case ‘p‘: 1344 icedemo.opt.turn_password = pj_str(pj_optarg); 1345 break; 1346 case ‘F‘: 1347 icedemo.opt.turn_fingerprint = PJ_TRUE; 1348 break; 1349 case ‘R‘: 1350 icedemo.opt.regular = PJ_TRUE; 1351 break; 1352 case ‘L‘: 1353 icedemo.opt.log_file = pj_optarg; 1354 break; 1355 default: 1356 printf("Argument \"%s\" is not valid. Use -h to see help", 1357 argv[pj_optind]); 1358 return 1; 1359 } 1360 } 1361 1362 status = icedemo_init(); 1363 if (status != PJ_SUCCESS) 1364 return 1; 1365 1366 icedemo_console(); 1367 1368 err_exit("Quitting..", PJ_SUCCESS); 1369 return 0; 1370 }
当然,其中的一些设置也可以写在代码文件中,该文不予介绍。
另外,也可以使用设置“继承的项目属性表”的方式进行设置,很多输出路径、编译路径、输出文件名称等繁琐的设置可以一次搞定,比如可以引入
..\pjproject-2.6\build\vs\pjproject-vs8-debug-static-defaults.vsprops
..\pjproject-2.6\build\vs\pjproject-vs8-win32-common-defaults.vsprops
在debug模式下就可以按照属性表的设置编译出对应的内容,对于要开发很多项目的情况,可以使用该方法,减少繁琐的设置。若项目工程不多,大可不必使用导入继承的项目属性表的方法,直接设置来的反而开发更快。
使用 pjsip 代码独立开发