blob: 4a0b4d7dd19134ade622541152017b77d727bdee [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $Id$ */
2/*
3 * Copyright (C) 2011-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 "vidgui.h"
20#include "vidwin.h"
21
22#if defined(PJ_WIN32)
23# define SDL_MAIN_HANDLED
24#endif
25
26#include <SDL.h>
27#include <assert.h>
28#include <QMessageBox>
29
30#define LOG_FILE "vidgui.log"
31#define THIS_FILE "vidgui.cpp"
32
33///////////////////////////////////////////////////////////////////////////
34//
35// SETTINGS
36//
37
38//
39// These configure SIP registration
40//
41#define USE_REGISTRATION 0
42#define SIP_DOMAIN "pjsip.org"
43#define SIP_USERNAME "vidgui"
44#define SIP_PASSWORD "secret"
45#define SIP_PORT 5080
46#define SIP_TCP 1
47
48//
49// NAT helper settings
50//
51#define USE_ICE 1
52#define USE_STUN 0
53#define STUN_SRV "stun.pjsip.org"
54
55//
56// Devices settings
57//
58#define DEFAULT_CAP_DEV PJMEDIA_VID_DEFAULT_CAPTURE_DEV
59//#define DEFAULT_CAP_DEV 1
60#define DEFAULT_REND_DEV PJMEDIA_VID_DEFAULT_RENDER_DEV
61
62
63//
64// End of Settings
65///////////////////////////////////////////////////////////////////////////
66
67
68MainWin *MainWin::theInstance_;
69
70MainWin::MainWin(QWidget *parent)
71: QWidget(parent), accountId_(-1), currentCall_(-1),
72 preview_on(false), video_(NULL), video_prev_(NULL)
73{
74 theInstance_ = this;
75
76 initLayout();
77 emit signalCallReleased();
78}
79
80MainWin::~MainWin()
81{
82 quit();
83 theInstance_ = NULL;
84}
85
86MainWin *MainWin::instance()
87{
88 return theInstance_;
89}
90
91void MainWin::initLayout()
92{
93 //statusBar_ = new QStatusBar(this);
94
95 /* main layout */
96 QHBoxLayout *hbox_main = new QHBoxLayout;
97 //QVBoxLayout *vbox_left = new QVBoxLayout;
98 vbox_left = new QVBoxLayout;
99 QVBoxLayout *vbox_right = new QVBoxLayout;
100 hbox_main->addLayout(vbox_left);
101 hbox_main->addLayout(vbox_right);
102
103 /* Left pane */
104 QHBoxLayout *hbox_url = new QHBoxLayout;
105 hbox_url->addWidget(new QLabel(tr("Url:")));
106 hbox_url->addWidget(url_=new QLineEdit(tr("sip:")), 1);
107 vbox_left->addLayout(hbox_url);
108
109 /* Right pane */
110 vbox_right->addWidget((localUri_ = new QLabel));
111 vbox_right->addWidget((vidEnabled_ = new QCheckBox(tr("Enable &video"))));
112 vbox_right->addWidget((previewButton_=new QPushButton(tr("Start &Preview"))));
113 vbox_right->addWidget((callButton_=new QPushButton(tr("Call"))));
114 vbox_right->addWidget((hangupButton_=new QPushButton(tr("Hangup"))));
115 vbox_right->addWidget((quitButton_=new QPushButton(tr("Quit"))));
116
117#if PJMEDIA_HAS_VIDEO
118 vidEnabled_->setCheckState(Qt::Checked);
119#else
120 vidEnabled_->setCheckState(Qt::Unchecked);
121 vidEnabled_->setEnabled(false);
122#endif
123
124 /* Outest layout */
125 QVBoxLayout *vbox_outest = new QVBoxLayout;
126 vbox_outest->addLayout(hbox_main);
127 vbox_outest->addWidget((statusBar_ = new QLabel));
128
129 setLayout(vbox_outest);
130
131 connect(previewButton_, SIGNAL(clicked()), this, SLOT(preview()));
132 connect(callButton_, SIGNAL(clicked()), this, SLOT(call()));
133 connect(hangupButton_, SIGNAL(clicked()), this, SLOT(hangup()));
134 connect(quitButton_, SIGNAL(clicked()), this, SLOT(quit()));
135 //connect(this, SIGNAL(close()), this, SLOT(quit()));
136 connect(vidEnabled_, SIGNAL(stateChanged(int)), this, SLOT(onVidEnabledChanged(int)));
137
138 // UI updates must be done in the UI thread!
139 connect(this, SIGNAL(signalNewCall(int, bool)),
140 this, SLOT(onNewCall(int, bool)));
141 connect(this, SIGNAL(signalCallReleased()),
142 this, SLOT(onCallReleased()));
143 connect(this, SIGNAL(signalInitVideoWindow()),
144 this, SLOT(initVideoWindow()));
145 connect(this, SIGNAL(signalShowStatus(const QString&)),
146 this, SLOT(doShowStatus(const QString&)));
147}
148
149void MainWin::quit()
150{
151 delete video_prev_;
152 video_prev_ = NULL;
153 delete video_;
154 video_ = NULL;
155
156 pjsua_destroy();
157 qApp->quit();
158}
159
160void MainWin::showStatus(const char *msg)
161{
162 PJ_LOG(3,(THIS_FILE, "%s", msg));
163
164 QString msg_ = QString::fromUtf8(msg);
165 emit signalShowStatus(msg_);
166}
167
168void MainWin::doShowStatus(const QString& msg)
169{
170 //statusBar_->showMessage(msg);
171 statusBar_->setText(msg);
172}
173
174void MainWin::showError(const char *title, pj_status_t status)
175{
176 char errmsg[PJ_ERR_MSG_SIZE];
177 char errline[120];
178
179 pj_strerror(status, errmsg, sizeof(errmsg));
180 snprintf(errline, sizeof(errline), "%s error: %s", title, errmsg);
181 showStatus(errline);
182}
183
184void MainWin::onVidEnabledChanged(int state)
185{
186 pjsua_call_setting call_setting;
187
188 if (currentCall_ == -1)
189 return;
190
191 pjsua_call_setting_default(&call_setting);
192 call_setting.vid_cnt = (state == Qt::Checked);
193
194 pjsua_call_reinvite2(currentCall_, &call_setting, NULL);
195}
196
197void MainWin::onNewCall(int cid, bool incoming)
198{
199 pjsua_call_info ci;
200
201 pj_assert(currentCall_ == -1);
202 currentCall_ = cid;
203
204 pjsua_call_get_info(cid, &ci);
205 url_->setText(ci.remote_info.ptr);
206 url_->setEnabled(false);
207 hangupButton_->setEnabled(true);
208
209 if (incoming) {
210 callButton_->setText(tr("Answer"));
211 callButton_->setEnabled(true);
212 } else {
213 callButton_->setEnabled(false);
214 }
215
216 //video_->setText(ci.remote_contact.ptr);
217 //video_->setWindowTitle(ci.remote_contact.ptr);
218}
219
220void MainWin::onCallReleased()
221{
222 url_->setEnabled(true);
223 callButton_->setEnabled(true);
224 callButton_->setText(tr("Call"));
225 hangupButton_->setEnabled(false);
226 currentCall_ = -1;
227
228 delete video_;
229 video_ = NULL;
230}
231
232void MainWin::preview()
233{
234 if (preview_on) {
235 delete video_prev_;
236 video_prev_ = NULL;
237
238 pjsua_vid_preview_stop(DEFAULT_CAP_DEV);
239
240 showStatus("Preview stopped");
241 previewButton_->setText(tr("Start &Preview"));
242 } else {
243 pjsua_vid_win_id wid;
244 pjsua_vid_win_info wi;
245 pjsua_vid_preview_param pre_param;
246 pj_status_t status;
247
248 pjsua_vid_preview_param_default(&pre_param);
249 pre_param.rend_id = DEFAULT_REND_DEV;
250 pre_param.show = PJ_FALSE;
251
252 status = pjsua_vid_preview_start(DEFAULT_CAP_DEV, &pre_param);
253 if (status != PJ_SUCCESS) {
254 char errmsg[PJ_ERR_MSG_SIZE];
255 pj_strerror(status, errmsg, sizeof(errmsg));
256 QMessageBox::critical(0, "Error creating preview", errmsg);
257 return;
258 }
259 wid = pjsua_vid_preview_get_win(DEFAULT_CAP_DEV);
260 pjsua_vid_win_get_info(wid, &wi);
261
262 video_prev_ = new VidWin(&wi.hwnd);
263 video_prev_->putIntoLayout(vbox_left);
264 //Using this will cause SDL window to display blank
265 //screen sometimes, probably because it's using different
266 //X11 Display
267 //status = pjsua_vid_win_set_show(wid, PJ_TRUE);
268 //This is handled by VidWin now
269 //video_prev_->show_sdl();
270 showStatus("Preview started");
271
272 previewButton_->setText(tr("Stop &Preview"));
273 }
274 preview_on = !preview_on;
275}
276
277
278void MainWin::call()
279{
280 if (callButton_->text() == "Answer") {
281 pjsua_call_setting call_setting;
282
283 pj_assert(currentCall_ != -1);
284
285 pjsua_call_setting_default(&call_setting);
286 call_setting.vid_cnt = (vidEnabled_->checkState()==Qt::Checked);
287
288 pjsua_call_answer2(currentCall_, &call_setting, 200, NULL, NULL);
289 callButton_->setEnabled(false);
290 } else {
291 pj_status_t status;
292 QString dst = url_->text();
293 char uri[256];
294 pjsua_call_setting call_setting;
295
296 pj_ansi_strncpy(uri, dst.toAscii().data(), sizeof(uri));
297 pj_str_t uri2 = pj_str((char*)uri);
298
299 pj_assert(currentCall_ == -1);
300
301 pjsua_call_setting_default(&call_setting);
302 call_setting.vid_cnt = (vidEnabled_->checkState()==Qt::Checked);
303
304 status = pjsua_call_make_call(accountId_, &uri2, &call_setting,
305 NULL, NULL, &currentCall_);
306 if (status != PJ_SUCCESS) {
307 showError("make call", status);
308 return;
309 }
310 }
311}
312
313void MainWin::hangup()
314{
315 pj_assert(currentCall_ != -1);
316 //pjsua_call_hangup(currentCall_, PJSIP_SC_BUSY_HERE, NULL, NULL);
317 pjsua_call_hangup_all();
318 emit signalCallReleased();
319}
320
321
322void MainWin::initVideoWindow()
323{
324 pjsua_call_info ci;
325 unsigned i;
326
327 if (currentCall_ == -1)
328 return;
329
330 delete video_;
331 video_ = NULL;
332
333 pjsua_call_get_info(currentCall_, &ci);
334 for (i = 0; i < ci.media_cnt; ++i) {
335 if ((ci.media[i].type == PJMEDIA_TYPE_VIDEO) &&
336 (ci.media[i].dir & PJMEDIA_DIR_DECODING))
337 {
338 pjsua_vid_win_info wi;
339 pjsua_vid_win_get_info(ci.media[i].stream.vid.win_in, &wi);
340
341 video_= new VidWin(&wi.hwnd);
342 video_->putIntoLayout(vbox_left);
343
344 break;
345 }
346 }
347}
348
349void MainWin::on_reg_state(pjsua_acc_id acc_id)
350{
351 pjsua_acc_info info;
352
353 pjsua_acc_get_info(acc_id, &info);
354
355 char reg_status[80];
356 char status[120];
357
358 if (!info.has_registration) {
359 pj_ansi_snprintf(reg_status, sizeof(reg_status), "%.*s",
360 (int)info.status_text.slen,
361 info.status_text.ptr);
362
363 } else {
364 pj_ansi_snprintf(reg_status, sizeof(reg_status),
365 "%d/%.*s (expires=%d)",
366 info.status,
367 (int)info.status_text.slen,
368 info.status_text.ptr,
369 info.expires);
370
371 }
372
373 snprintf(status, sizeof(status),
374 "%.*s: %s\n",
375 (int)info.acc_uri.slen, info.acc_uri.ptr,
376 reg_status);
377 showStatus(status);
378}
379
380void MainWin::on_call_state(pjsua_call_id call_id, pjsip_event *e)
381{
382 pjsua_call_info ci;
383
384 PJ_UNUSED_ARG(e);
385
386 pjsua_call_get_info(call_id, &ci);
387
388 if (currentCall_ == -1 && ci.state < PJSIP_INV_STATE_DISCONNECTED &&
389 ci.role == PJSIP_ROLE_UAC)
390 {
391 emit signalNewCall(call_id, false);
392 }
393
394 char status[80];
395 if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
396 snprintf(status, sizeof(status), "Call is %s (%s)",
397 ci.state_text.ptr,
398 ci.last_status_text.ptr);
399 showStatus(status);
400 emit signalCallReleased();
401 } else {
402 snprintf(status, sizeof(status), "Call is %s", pjsip_inv_state_name(ci.state));
403 showStatus(status);
404 }
405}
406
407void MainWin::on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
408 pjsip_rx_data *rdata)
409{
410 PJ_UNUSED_ARG(acc_id);
411 PJ_UNUSED_ARG(rdata);
412
413 if (currentCall_ != -1) {
414 pjsua_call_answer(call_id, PJSIP_SC_BUSY_HERE, NULL, NULL);
415 return;
416 }
417
418 emit signalNewCall(call_id, true);
419
420 pjsua_call_info ci;
421 char status[80];
422
423 pjsua_call_get_info(call_id, &ci);
424 snprintf(status, sizeof(status), "Incoming call from %.*s",
425 (int)ci.remote_info.slen, ci.remote_info.ptr);
426 showStatus(status);
427}
428
429void MainWin::on_call_media_state(pjsua_call_id call_id)
430{
431 pjsua_call_info ci;
432
433 pjsua_call_get_info(call_id, &ci);
434
435 for (unsigned i=0; i<ci.media_cnt; ++i) {
436 if (ci.media[i].type == PJMEDIA_TYPE_AUDIO) {
437 switch (ci.media[i].status) {
438 case PJSUA_CALL_MEDIA_ACTIVE:
439 pjsua_conf_connect(ci.media[i].stream.aud.conf_slot, 0);
440 pjsua_conf_connect(0, ci.media[i].stream.aud.conf_slot);
441 break;
442 default:
443 break;
444 }
445 } else if (ci.media[i].type == PJMEDIA_TYPE_VIDEO) {
446 emit signalInitVideoWindow();
447 }
448 }
449}
450
451//
452// pjsua callbacks
453//
454static void on_reg_state(pjsua_acc_id acc_id)
455{
456 MainWin::instance()->on_reg_state(acc_id);
457}
458
459static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
460{
461 MainWin::instance()->on_call_state(call_id, e);
462}
463
464static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
465 pjsip_rx_data *rdata)
466{
467 MainWin::instance()->on_incoming_call(acc_id, call_id, rdata);
468}
469
470static void on_call_media_state(pjsua_call_id call_id)
471{
472 MainWin::instance()->on_call_media_state(call_id);
473}
474
475//
476// initStack()
477//
478bool MainWin::initStack()
479{
480 pj_status_t status;
481
482 //showStatus("Creating stack..");
483 status = pjsua_create();
484 if (status != PJ_SUCCESS) {
485 showError("pjsua_create", status);
486 return false;
487 }
488
489 showStatus("Initializing stack..");
490
491 pjsua_config ua_cfg;
492 pjsua_config_default(&ua_cfg);
493 pjsua_callback ua_cb;
494 pj_bzero(&ua_cb, sizeof(ua_cb));
495 ua_cfg.cb.on_reg_state = &::on_reg_state;
496 ua_cfg.cb.on_call_state = &::on_call_state;
497 ua_cfg.cb.on_incoming_call = &::on_incoming_call;
498 ua_cfg.cb.on_call_media_state = &::on_call_media_state;
499#if USE_STUN
500 ua_cfg.stun_srv_cnt = 1;
501 ua_cfg.stun_srv[0] = pj_str((char*)STUN_SRV);
502#endif
503
504 pjsua_logging_config log_cfg;
505 pjsua_logging_config_default(&log_cfg);
506 log_cfg.log_filename = pj_str((char*)LOG_FILE);
507
508 pjsua_media_config med_cfg;
509 pjsua_media_config_default(&med_cfg);
510 med_cfg.enable_ice = USE_ICE;
511
512 status = pjsua_init(&ua_cfg, &log_cfg, &med_cfg);
513 if (status != PJ_SUCCESS) {
514 showError("pjsua_init", status);
515 goto on_error;
516 }
517
518 //
519 // Create UDP and TCP transports
520 //
521 pjsua_transport_config udp_cfg;
522 pjsua_transport_id udp_id;
523 pjsua_transport_config_default(&udp_cfg);
524 udp_cfg.port = SIP_PORT;
525
526 status = pjsua_transport_create(PJSIP_TRANSPORT_UDP,
527 &udp_cfg, &udp_id);
528 if (status != PJ_SUCCESS) {
529 showError("UDP transport creation", status);
530 goto on_error;
531 }
532
533 pjsua_transport_info udp_info;
534 status = pjsua_transport_get_info(udp_id, &udp_info);
535 if (status != PJ_SUCCESS) {
536 showError("UDP transport info", status);
537 goto on_error;
538 }
539
540#if SIP_TCP
541 pjsua_transport_config tcp_cfg;
542 pjsua_transport_config_default(&tcp_cfg);
543 tcp_cfg.port = 0;
544
545 status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
546 &tcp_cfg, NULL);
547 if (status != PJ_SUCCESS) {
548 showError("TCP transport creation", status);
549 goto on_error;
550 }
551#endif
552
553 //
554 // Create account
555 //
556 pjsua_acc_config acc_cfg;
557 pjsua_acc_config_default(&acc_cfg);
558#if USE_REGISTRATION
559 acc_cfg.id = pj_str( (char*)"<sip:" SIP_USERNAME "@" SIP_DOMAIN ">");
560 acc_cfg.reg_uri = pj_str((char*) ("sip:" SIP_DOMAIN));
561 acc_cfg.cred_count = 1;
562 acc_cfg.cred_info[0].realm = pj_str((char*)"*");
563 acc_cfg.cred_info[0].scheme = pj_str((char*)"digest");
564 acc_cfg.cred_info[0].username = pj_str((char*)SIP_USERNAME);
565 acc_cfg.cred_info[0].data = pj_str((char*)SIP_PASSWORD);
566
567# if SIP_TCP
568 acc_cfg.proxy[acc_cfg.proxy_cnt++] = pj_str((char*) "<sip:" SIP_DOMAIN ";transport=tcp>");
569# endif
570
571#else
572 char sip_id[80];
573 snprintf(sip_id, sizeof(sip_id),
574 "sip:%s@%.*s:%u", SIP_USERNAME,
575 (int)udp_info.local_name.host.slen,
576 udp_info.local_name.host.ptr,
577 udp_info.local_name.port);
578 acc_cfg.id = pj_str(sip_id);
579#endif
580
581 acc_cfg.vid_cap_dev = DEFAULT_CAP_DEV;
582 acc_cfg.vid_rend_dev = DEFAULT_REND_DEV;
583 acc_cfg.vid_in_auto_show = PJ_TRUE;
584 acc_cfg.vid_out_auto_transmit = PJ_TRUE;
585
586 status = pjsua_acc_add(&acc_cfg, PJ_TRUE, &accountId_);
587 if (status != PJ_SUCCESS) {
588 showError("Account creation", status);
589 goto on_error;
590 }
591
592 localUri_->setText(acc_cfg.id.ptr);
593
594 //
595 // Start pjsua!
596 //
597 showStatus("Starting stack..");
598 status = pjsua_start();
599 if (status != PJ_SUCCESS) {
600 showError("pjsua_start", status);
601 goto on_error;
602 }
603
604 showStatus("Ready");
605
606 return true;
607
608on_error:
609 pjsua_destroy();
610 return false;
611}
612
613/*
614 * A simple registrar, invoked by default_mod_on_rx_request()
615 */
616static void simple_registrar(pjsip_rx_data *rdata)
617{
618 pjsip_tx_data *tdata;
619 const pjsip_expires_hdr *exp;
620 const pjsip_hdr *h;
621 unsigned cnt = 0;
622 pjsip_generic_string_hdr *srv;
623 pj_status_t status;
624
625 status = pjsip_endpt_create_response(pjsua_get_pjsip_endpt(),
626 rdata, 200, NULL, &tdata);
627 if (status != PJ_SUCCESS)
628 return;
629
630 exp = (pjsip_expires_hdr*)
631 pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
632
633 h = rdata->msg_info.msg->hdr.next;
634 while (h != &rdata->msg_info.msg->hdr) {
635 if (h->type == PJSIP_H_CONTACT) {
636 const pjsip_contact_hdr *c = (const pjsip_contact_hdr*)h;
637 int e = c->expires;
638
639 if (e < 0) {
640 if (exp)
641 e = exp->ivalue;
642 else
643 e = 3600;
644 }
645
646 if (e > 0) {
647 pjsip_contact_hdr *nc = (pjsip_contact_hdr*)
648 pjsip_hdr_clone(tdata->pool, h);
649 nc->expires = e;
650 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)nc);
651 ++cnt;
652 }
653 }
654 h = h->next;
655 }
656
657 srv = pjsip_generic_string_hdr_create(tdata->pool, NULL, NULL);
658 srv->name = pj_str((char*)"Server");
659 srv->hvalue = pj_str((char*)"pjsua simple registrar");
660 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)srv);
661
662 pjsip_endpt_send_response2(pjsua_get_pjsip_endpt(),
663 rdata, tdata, NULL, NULL);
664}
665
666/* Notification on incoming request */
667static pj_bool_t default_mod_on_rx_request(pjsip_rx_data *rdata)
668{
669 /* Simple registrar */
670 if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
671 &pjsip_register_method) == 0)
672 {
673 simple_registrar(rdata);
674 return PJ_TRUE;
675 }
676
677 return PJ_FALSE;
678}
679
680/* The module instance. */
681static pjsip_module mod_default_handler =
682{
683 NULL, NULL, /* prev, next. */
684 { (char*)"mod-default-handler", 19 }, /* Name. */
685 -1, /* Id */
686 PJSIP_MOD_PRIORITY_APPLICATION+99, /* Priority */
687 NULL, /* load() */
688 NULL, /* start() */
689 NULL, /* stop() */
690 NULL, /* unload() */
691 &default_mod_on_rx_request, /* on_rx_request() */
692 NULL, /* on_rx_response() */
693 NULL, /* on_tx_request. */
694 NULL, /* on_tx_response() */
695 NULL, /* on_tsx_state() */
696
697};
698
699int main(int argc, char *argv[])
700{
701 /* At least on Linux, we have to initialize SDL video subsystem prior to
702 * creating/initializing QApplication, otherwise we'll segfault miserably
703 * in SDL_CreateWindow(). Here's a stack trace if you're interested:
704
705 Thread [7] (Suspended: Signal 'SIGSEGV' received. Description: Segmentation fault.)
706 13 XCreateIC()
707 12 SetupWindowData()
708 11 X11_CreateWindow()
709 10 SDL_CreateWindow()
710 ..
711 */
712 if ( SDL_InitSubSystem(SDL_INIT_VIDEO) < 0 ) {
713 printf("Unable to init SDL: %s\n", SDL_GetError());
714 return 1;
715 }
716
717 QApplication app(argc, argv);
718
719 MainWin win;
720 win.show();
721
722 if (!win.initStack()) {
723 win.quit();
724 return 1;
725 }
726
727 /* We want to be registrar too! */
728 if (pjsua_get_pjsip_endpt()) {
729 pjsip_endpt_register_module(pjsua_get_pjsip_endpt(),
730 &mod_default_handler);
731 }
732
733 return app.exec();
734}
735