blob: 2521193529fbf28fb8f88b6457d5cdf47de096dd [file] [log] [blame]
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001/* $Id$ */
Tristan Matthews0a329cc2013-07-17 13:20:14 -04002/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20#include "pjsua_app.h"
21
22#define THIS_FILE "pjsua_app.c"
23
24//#define STEREO_DEMO
25//#define TRANSPORT_ADAPTER_SAMPLE
26//#define HAVE_MULTIPART_TEST
27
28/* Ringtones US UK */
29#define RINGBACK_FREQ1 440 /* 400 */
30#define RINGBACK_FREQ2 480 /* 450 */
31#define RINGBACK_ON 2000 /* 400 */
32#define RINGBACK_OFF 4000 /* 200 */
33#define RINGBACK_CNT 1 /* 2 */
34#define RINGBACK_INTERVAL 4000 /* 2000 */
35
36#define RING_FREQ1 800
37#define RING_FREQ2 640
38#define RING_ON 200
39#define RING_OFF 100
40#define RING_CNT 3
41#define RING_INTERVAL 3000
42
43#define current_acc pjsua_acc_get_default()
44
45#ifdef STEREO_DEMO
46static void stereo_demo();
47#endif
48
49static void ringback_start(pjsua_call_id call_id);
50static void ring_start(pjsua_call_id call_id);
51static void ring_stop(pjsua_call_id call_id);
52static pj_status_t app_init();
53static pj_status_t app_destroy();
54
55static pjsua_app_cfg_t app_cfg;
56pj_str_t uri_arg;
57pj_bool_t app_running = PJ_FALSE;
58
59/*****************************************************************************
60 * Configuration manipulation
61 */
62
63/*****************************************************************************
64 * Callback
65 */
66static void ringback_start(pjsua_call_id call_id)
67{
68 if (app_config.no_tones)
69 return;
70
71 if (app_config.call_data[call_id].ringback_on)
72 return;
73
74 app_config.call_data[call_id].ringback_on = PJ_TRUE;
75
76 if (++app_config.ringback_cnt==1 &&
77 app_config.ringback_slot!=PJSUA_INVALID_ID)
78 {
79 pjsua_conf_connect(app_config.ringback_slot, 0);
80 }
81}
82
83static void ring_stop(pjsua_call_id call_id)
84{
85 if (app_config.no_tones)
86 return;
87
88 if (app_config.call_data[call_id].ringback_on) {
89 app_config.call_data[call_id].ringback_on = PJ_FALSE;
90
91 pj_assert(app_config.ringback_cnt>0);
92 if (--app_config.ringback_cnt == 0 &&
93 app_config.ringback_slot!=PJSUA_INVALID_ID)
94 {
95 pjsua_conf_disconnect(app_config.ringback_slot, 0);
96 pjmedia_tonegen_rewind(app_config.ringback_port);
97 }
98 }
99
100 if (app_config.call_data[call_id].ring_on) {
101 app_config.call_data[call_id].ring_on = PJ_FALSE;
102
103 pj_assert(app_config.ring_cnt>0);
104 if (--app_config.ring_cnt == 0 &&
105 app_config.ring_slot!=PJSUA_INVALID_ID)
106 {
107 pjsua_conf_disconnect(app_config.ring_slot, 0);
108 pjmedia_tonegen_rewind(app_config.ring_port);
109 }
110 }
111}
112
113static void ring_start(pjsua_call_id call_id)
114{
115 if (app_config.no_tones)
116 return;
117
118 if (app_config.call_data[call_id].ring_on)
119 return;
120
121 app_config.call_data[call_id].ring_on = PJ_TRUE;
122
123 if (++app_config.ring_cnt==1 &&
124 app_config.ring_slot!=PJSUA_INVALID_ID)
125 {
126 pjsua_conf_connect(app_config.ring_slot, 0);
127 }
128}
129
130/* Callback from timer when the maximum call duration has been
131 * exceeded.
132 */
133static void call_timeout_callback(pj_timer_heap_t *timer_heap,
134 struct pj_timer_entry *entry)
135{
136 pjsua_call_id call_id = entry->id;
137 pjsua_msg_data msg_data;
138 pjsip_generic_string_hdr warn;
139 pj_str_t hname = pj_str("Warning");
140 pj_str_t hvalue = pj_str("399 pjsua \"Call duration exceeded\"");
141
142 PJ_UNUSED_ARG(timer_heap);
143
144 if (call_id == PJSUA_INVALID_ID) {
145 PJ_LOG(1,(THIS_FILE, "Invalid call ID in timer callback"));
146 return;
147 }
148
149 /* Add warning header */
150 pjsua_msg_data_init(&msg_data);
151 pjsip_generic_string_hdr_init2(&warn, &hname, &hvalue);
152 pj_list_push_back(&msg_data.hdr_list, &warn);
153
154 /* Call duration has been exceeded; disconnect the call */
155 PJ_LOG(3,(THIS_FILE, "Duration (%d seconds) has been exceeded "
156 "for call %d, disconnecting the call",
157 app_config.duration, call_id));
158 entry->id = PJSUA_INVALID_ID;
159 pjsua_call_hangup(call_id, 200, NULL, &msg_data);
160}
161
162/*
163 * Handler when invite state has changed.
164 */
165static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
166{
167 pjsua_call_info call_info;
168
169 PJ_UNUSED_ARG(e);
170
171 pjsua_call_get_info(call_id, &call_info);
172
173 if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
174
175 /* Stop all ringback for this call */
176 ring_stop(call_id);
177
178 /* Cancel duration timer, if any */
179 if (app_config.call_data[call_id].timer.id != PJSUA_INVALID_ID) {
180 app_call_data *cd = &app_config.call_data[call_id];
181 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
182
183 cd->timer.id = PJSUA_INVALID_ID;
184 pjsip_endpt_cancel_timer(endpt, &cd->timer);
185 }
186
187 /* Rewind play file when hangup automatically,
188 * since file is not looped
189 */
190 if (app_config.auto_play_hangup)
191 pjsua_player_set_pos(app_config.wav_id, 0);
192
193
194 PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]",
195 call_id,
196 call_info.last_status,
197 call_info.last_status_text.ptr));
198
199 if (call_id == current_call) {
200 find_next_call();
201 }
202
203 /* Dump media state upon disconnected */
204 if (1) {
205 PJ_LOG(5,(THIS_FILE,
206 "Call %d disconnected, dumping media stats..",
207 call_id));
208 log_call_dump(call_id);
209 }
210
211 } else {
212
213 if (app_config.duration != PJSUA_APP_NO_LIMIT_DURATION &&
214 call_info.state == PJSIP_INV_STATE_CONFIRMED)
215 {
216 /* Schedule timer to hangup call after the specified duration */
217 app_call_data *cd = &app_config.call_data[call_id];
218 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
219 pj_time_val delay;
220
221 cd->timer.id = call_id;
222 delay.sec = app_config.duration;
223 delay.msec = 0;
224 pjsip_endpt_schedule_timer(endpt, &cd->timer, &delay);
225 }
226
227 if (call_info.state == PJSIP_INV_STATE_EARLY) {
228 int code;
229 pj_str_t reason;
230 pjsip_msg *msg;
231
232 /* This can only occur because of TX or RX message */
233 pj_assert(e->type == PJSIP_EVENT_TSX_STATE);
234
235 if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
236 msg = e->body.tsx_state.src.rdata->msg_info.msg;
237 } else {
238 msg = e->body.tsx_state.src.tdata->msg;
239 }
240
241 code = msg->line.status.code;
242 reason = msg->line.status.reason;
243
244 /* Start ringback for 180 for UAC unless there's SDP in 180 */
245 if (call_info.role==PJSIP_ROLE_UAC && code==180 &&
246 msg->body == NULL &&
247 call_info.media_status==PJSUA_CALL_MEDIA_NONE)
248 {
249 ringback_start(call_id);
250 }
251
252 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s (%d %.*s)",
253 call_id, call_info.state_text.ptr,
254 code, (int)reason.slen, reason.ptr));
255 } else {
256 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s",
257 call_id,
258 call_info.state_text.ptr));
259 }
260
261 if (current_call==PJSUA_INVALID_ID)
262 current_call = call_id;
263
264 }
265}
266
267/**
268 * Handler when there is incoming call.
269 */
270static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
271 pjsip_rx_data *rdata)
272{
273 pjsua_call_info call_info;
274
275 PJ_UNUSED_ARG(acc_id);
276 PJ_UNUSED_ARG(rdata);
277
278 pjsua_call_get_info(call_id, &call_info);
279
280 if (current_call==PJSUA_INVALID_ID)
281 current_call = call_id;
282
283#ifdef USE_GUI
284 if (!showNotification(call_id))
285 return;
286#endif
287
288 /* Start ringback */
289 ring_start(call_id);
290
291 if (app_config.auto_answer > 0) {
292 pjsua_call_setting call_opt;
293
294 pjsua_call_setting_default(&call_opt);
295 call_opt.aud_cnt = app_config.aud_cnt;
296 call_opt.vid_cnt = app_config.vid.vid_cnt;
297
298 pjsua_call_answer2(call_id, &call_opt, app_config.auto_answer, NULL,
299 NULL);
300 }
301
302 if (app_config.auto_answer < 200) {
303 char notif_st[80] = {0};
304
305#if PJSUA_HAS_VIDEO
306 if (call_info.rem_offerer && call_info.rem_vid_cnt) {
307 snprintf(notif_st, sizeof(notif_st),
308 "To %s the video, type \"vid %s\" first, "
309 "before answering the call!\n",
310 (app_config.vid.vid_cnt? "reject":"accept"),
311 (app_config.vid.vid_cnt? "disable":"enable"));
312 }
313#endif
314
315 PJ_LOG(3,(THIS_FILE,
316 "Incoming call for account %d!\n"
317 "Media count: %d audio & %d video\n"
318 "%s"
319 "From: %s\n"
320 "To: %s\n"
321 "Press %s to answer or %s to reject call",
322 acc_id,
323 call_info.rem_aud_cnt,
324 call_info.rem_vid_cnt,
325 notif_st,
326 call_info.remote_info.ptr,
327 call_info.local_info.ptr,
328 (app_config.use_cli?"c a":"a"),
329 (app_config.use_cli?"c g":"h")));
330 }
331}
332
333/*
334 * Handler when a transaction within a call has changed state.
335 */
336static void on_call_tsx_state(pjsua_call_id call_id,
337 pjsip_transaction *tsx,
338 pjsip_event *e)
339{
340 const pjsip_method info_method =
341 {
342 PJSIP_OTHER_METHOD,
343 { "INFO", 4 }
344 };
345
346 if (pjsip_method_cmp(&tsx->method, &info_method)==0) {
347 /*
348 * Handle INFO method.
349 */
350 const pj_str_t STR_APPLICATION = { "application", 11};
351 const pj_str_t STR_DTMF_RELAY = { "dtmf-relay", 10 };
352 pjsip_msg_body *body = NULL;
353 pj_bool_t dtmf_info = PJ_FALSE;
354
355 if (tsx->role == PJSIP_ROLE_UAC) {
356 if (e->body.tsx_state.type == PJSIP_EVENT_TX_MSG)
357 body = e->body.tsx_state.src.tdata->msg->body;
358 else
359 body = e->body.tsx_state.tsx->last_tx->msg->body;
360 } else {
361 if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
362 body = e->body.tsx_state.src.rdata->msg_info.msg->body;
363 }
364
365 /* Check DTMF content in the INFO message */
366 if (body && body->len &&
367 pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
368 pj_stricmp(&body->content_type.subtype, &STR_DTMF_RELAY)==0)
369 {
370 dtmf_info = PJ_TRUE;
371 }
372
373 if (dtmf_info && tsx->role == PJSIP_ROLE_UAC &&
374 (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
375 (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
376 e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED)))
377 {
378 /* Status of outgoing INFO request */
379 if (tsx->status_code >= 200 && tsx->status_code < 300) {
380 PJ_LOG(4,(THIS_FILE,
381 "Call %d: DTMF sent successfully with INFO",
382 call_id));
383 } else if (tsx->status_code >= 300) {
384 PJ_LOG(4,(THIS_FILE,
385 "Call %d: Failed to send DTMF with INFO: %d/%.*s",
386 call_id,
387 tsx->status_code,
388 (int)tsx->status_text.slen,
389 tsx->status_text.ptr));
390 }
391 } else if (dtmf_info && tsx->role == PJSIP_ROLE_UAS &&
392 tsx->state == PJSIP_TSX_STATE_TRYING)
393 {
394 /* Answer incoming INFO with 200/OK */
395 pjsip_rx_data *rdata;
396 pjsip_tx_data *tdata;
397 pj_status_t status;
398
399 rdata = e->body.tsx_state.src.rdata;
400
401 if (rdata->msg_info.msg->body) {
402 status = pjsip_endpt_create_response(tsx->endpt, rdata,
403 200, NULL, &tdata);
404 if (status == PJ_SUCCESS)
405 status = pjsip_tsx_send_msg(tsx, tdata);
406
407 PJ_LOG(3,(THIS_FILE, "Call %d: incoming INFO:\n%.*s",
408 call_id,
409 (int)rdata->msg_info.msg->body->len,
410 rdata->msg_info.msg->body->data));
411 } else {
412 status = pjsip_endpt_create_response(tsx->endpt, rdata,
413 400, NULL, &tdata);
414 if (status == PJ_SUCCESS)
415 status = pjsip_tsx_send_msg(tsx, tdata);
416 }
417 }
418 }
419}
420
421/* General processing for media state. "mi" is the media index */
422static void on_call_generic_media_state(pjsua_call_info *ci, unsigned mi,
423 pj_bool_t *has_error)
424{
425 const char *status_name[] = {
426 "None",
427 "Active",
428 "Local hold",
429 "Remote hold",
430 "Error"
431 };
432
433 PJ_UNUSED_ARG(has_error);
434
435 pj_assert(ci->media[mi].status <= PJ_ARRAY_SIZE(status_name));
436 pj_assert(PJSUA_CALL_MEDIA_ERROR == 4);
437
438 PJ_LOG(4,(THIS_FILE, "Call %d media %d [type=%s], status is %s",
439 ci->id, mi, pjmedia_type_name(ci->media[mi].type),
440 status_name[ci->media[mi].status]));
441}
442
443/* Process audio media state. "mi" is the media index. */
444static void on_call_audio_state(pjsua_call_info *ci, unsigned mi,
445 pj_bool_t *has_error)
446{
447 PJ_UNUSED_ARG(has_error);
448
449 /* Stop ringback */
450 ring_stop(ci->id);
451
452 /* Connect ports appropriately when media status is ACTIVE or REMOTE HOLD,
453 * otherwise we should NOT connect the ports.
454 */
455 if (ci->media[mi].status == PJSUA_CALL_MEDIA_ACTIVE ||
456 ci->media[mi].status == PJSUA_CALL_MEDIA_REMOTE_HOLD)
457 {
458 pj_bool_t connect_sound = PJ_TRUE;
459 pj_bool_t disconnect_mic = PJ_FALSE;
460 pjsua_conf_port_id call_conf_slot;
461
462 call_conf_slot = ci->media[mi].stream.aud.conf_slot;
463
464 /* Loopback sound, if desired */
465 if (app_config.auto_loop) {
466 pjsua_conf_connect(call_conf_slot, call_conf_slot);
467 connect_sound = PJ_FALSE;
468 }
469
470 /* Automatically record conversation, if desired */
471 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
472 pjsua_conf_connect(call_conf_slot, app_config.rec_port);
473 }
474
475 /* Stream a file, if desired */
476 if ((app_config.auto_play || app_config.auto_play_hangup) &&
477 app_config.wav_port != PJSUA_INVALID_ID)
478 {
479 pjsua_conf_connect(app_config.wav_port, call_conf_slot);
480 connect_sound = PJ_FALSE;
481 }
482
483 /* Stream AVI, if desired */
484 if (app_config.avi_auto_play &&
485 app_config.avi_def_idx != PJSUA_INVALID_ID &&
486 app_config.avi[app_config.avi_def_idx].slot != PJSUA_INVALID_ID)
487 {
488 pjsua_conf_connect(app_config.avi[app_config.avi_def_idx].slot,
489 call_conf_slot);
490 disconnect_mic = PJ_TRUE;
491 }
492
493 /* Put call in conference with other calls, if desired */
494 if (app_config.auto_conf) {
495 pjsua_call_id call_ids[PJSUA_MAX_CALLS];
496 unsigned call_cnt=PJ_ARRAY_SIZE(call_ids);
497 unsigned i;
498
499 /* Get all calls, and establish media connection between
500 * this call and other calls.
501 */
502 pjsua_enum_calls(call_ids, &call_cnt);
503
504 for (i=0; i<call_cnt; ++i) {
505 if (call_ids[i] == ci->id)
506 continue;
507
508 if (!pjsua_call_has_media(call_ids[i]))
509 continue;
510
511 pjsua_conf_connect(call_conf_slot,
512 pjsua_call_get_conf_port(call_ids[i]));
513 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
514 call_conf_slot);
515
516 /* Automatically record conversation, if desired */
517 if (app_config.auto_rec && app_config.rec_port !=
518 PJSUA_INVALID_ID)
519 {
520 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
521 app_config.rec_port);
522 }
523
524 }
525
526 /* Also connect call to local sound device */
527 connect_sound = PJ_TRUE;
528 }
529
530 /* Otherwise connect to sound device */
531 if (connect_sound) {
532 pjsua_conf_connect(call_conf_slot, 0);
533 if (!disconnect_mic)
534 pjsua_conf_connect(0, call_conf_slot);
535
536 /* Automatically record conversation, if desired */
537 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID)
538 {
539 pjsua_conf_connect(call_conf_slot, app_config.rec_port);
540 pjsua_conf_connect(0, app_config.rec_port);
541 }
542 }
543 }
544}
545
546/* Process video media state. "mi" is the media index. */
547static void on_call_video_state(pjsua_call_info *ci, unsigned mi,
548 pj_bool_t *has_error)
549{
550 if (ci->media_status != PJSUA_CALL_MEDIA_ACTIVE)
551 return;
552
553 arrange_window(ci->media[mi].stream.vid.win_in);
554
555 PJ_UNUSED_ARG(has_error);
556}
557
558/*
559 * Callback on media state changed event.
560 * The action may connect the call to sound device, to file, or
561 * to loop the call.
562 */
563static void on_call_media_state(pjsua_call_id call_id)
564{
565 pjsua_call_info call_info;
566 unsigned mi;
567 pj_bool_t has_error = PJ_FALSE;
568
569 pjsua_call_get_info(call_id, &call_info);
570
571 for (mi=0; mi<call_info.media_cnt; ++mi) {
572 on_call_generic_media_state(&call_info, mi, &has_error);
573
574 switch (call_info.media[mi].type) {
575 case PJMEDIA_TYPE_AUDIO:
576 on_call_audio_state(&call_info, mi, &has_error);
577 break;
578 case PJMEDIA_TYPE_VIDEO:
579 on_call_video_state(&call_info, mi, &has_error);
580 break;
581 default:
582 /* Make gcc happy about enum not handled by switch/case */
583 break;
584 }
585 }
586
587 if (has_error) {
588 pj_str_t reason = pj_str("Media failed");
589 pjsua_call_hangup(call_id, 500, &reason, NULL);
590 }
591
592#if PJSUA_HAS_VIDEO
593 /* Check if remote has just tried to enable video */
594 if (call_info.rem_offerer && call_info.rem_vid_cnt)
595 {
596 int vid_idx;
597
598 /* Check if there is active video */
599 vid_idx = pjsua_call_get_vid_stream_idx(call_id);
600 if (vid_idx == -1 || call_info.media[vid_idx].dir == PJMEDIA_DIR_NONE) {
601 PJ_LOG(3,(THIS_FILE,
602 "Just rejected incoming video offer on call %d, "
603 "use \"vid call enable %d\" or \"vid call add\" to "
604 "enable video!", call_id, vid_idx));
605 }
606 }
607#endif
608}
609
610/*
611 * DTMF callback.
612 */
613static void call_on_dtmf_callback(pjsua_call_id call_id, int dtmf)
614{
615 PJ_LOG(3,(THIS_FILE, "Incoming DTMF on call %d: %c", call_id, dtmf));
616}
617
618/*
619 * Redirection handler.
620 */
621static pjsip_redirect_op call_on_redirected(pjsua_call_id call_id,
622 const pjsip_uri *target,
623 const pjsip_event *e)
624{
625 PJ_UNUSED_ARG(e);
626
627 if (app_config.redir_op == PJSIP_REDIRECT_PENDING) {
628 char uristr[PJSIP_MAX_URL_SIZE];
629 int len;
630
631 len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, target, uristr,
632 sizeof(uristr));
633 if (len < 1) {
634 pj_ansi_strcpy(uristr, "--URI too long--");
635 }
636
637 PJ_LOG(3,(THIS_FILE, "Call %d is being redirected to %.*s. "
638 "Press 'Ra' to accept+replace To header, 'RA' to accept, "
639 "'Rr' to reject, or 'Rd' to disconnect.",
640 call_id, len, uristr));
641 }
642
643 return app_config.redir_op;
644}
645
646/*
647 * Handler registration status has changed.
648 */
649static void on_reg_state(pjsua_acc_id acc_id)
650{
651 PJ_UNUSED_ARG(acc_id);
652
653 // Log already written.
654}
655
656/*
657 * Handler for incoming presence subscription request
658 */
659static void on_incoming_subscribe(pjsua_acc_id acc_id,
660 pjsua_srv_pres *srv_pres,
661 pjsua_buddy_id buddy_id,
662 const pj_str_t *from,
663 pjsip_rx_data *rdata,
664 pjsip_status_code *code,
665 pj_str_t *reason,
666 pjsua_msg_data *msg_data)
667{
668 /* Just accept the request (the default behavior) */
669 PJ_UNUSED_ARG(acc_id);
670 PJ_UNUSED_ARG(srv_pres);
671 PJ_UNUSED_ARG(buddy_id);
672 PJ_UNUSED_ARG(from);
673 PJ_UNUSED_ARG(rdata);
674 PJ_UNUSED_ARG(code);
675 PJ_UNUSED_ARG(reason);
676 PJ_UNUSED_ARG(msg_data);
677}
678
679
680/*
681 * Handler on buddy state changed.
682 */
683static void on_buddy_state(pjsua_buddy_id buddy_id)
684{
685 pjsua_buddy_info info;
686 pjsua_buddy_get_info(buddy_id, &info);
687
688 PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s, subscription state is %s "
689 "(last termination reason code=%d %.*s)",
690 (int)info.uri.slen,
691 info.uri.ptr,
692 (int)info.status_text.slen,
693 info.status_text.ptr,
694 info.sub_state_name,
695 info.sub_term_code,
696 (int)info.sub_term_reason.slen,
697 info.sub_term_reason.ptr));
698}
699
700
701/*
702 * Subscription state has changed.
703 */
704static void on_buddy_evsub_state(pjsua_buddy_id buddy_id,
705 pjsip_evsub *sub,
706 pjsip_event *event)
707{
708 char event_info[80];
709
710 PJ_UNUSED_ARG(sub);
711
712 event_info[0] = '\0';
713
714 if (event->type == PJSIP_EVENT_TSX_STATE &&
715 event->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
716 {
717 pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
718 snprintf(event_info, sizeof(event_info),
719 " (RX %s)",
720 pjsip_rx_data_get_info(rdata));
721 }
722
723 PJ_LOG(4,(THIS_FILE,
724 "Buddy %d: subscription state: %s (event: %s%s)",
725 buddy_id, pjsip_evsub_get_state_name(sub),
726 pjsip_event_str(event->type),
727 event_info));
728
729}
730
731
732/**
733 * Incoming IM message (i.e. MESSAGE request)!
734 */
735static void on_pager(pjsua_call_id call_id, const pj_str_t *from,
736 const pj_str_t *to, const pj_str_t *contact,
737 const pj_str_t *mime_type, const pj_str_t *text)
738{
739 /* Note: call index may be -1 */
740 PJ_UNUSED_ARG(call_id);
741 PJ_UNUSED_ARG(to);
742 PJ_UNUSED_ARG(contact);
743 PJ_UNUSED_ARG(mime_type);
744
745 PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s (%.*s)",
746 (int)from->slen, from->ptr,
747 (int)text->slen, text->ptr,
748 (int)mime_type->slen, mime_type->ptr));
749}
750
751
752/**
753 * Received typing indication
754 */
755static void on_typing(pjsua_call_id call_id, const pj_str_t *from,
756 const pj_str_t *to, const pj_str_t *contact,
757 pj_bool_t is_typing)
758{
759 PJ_UNUSED_ARG(call_id);
760 PJ_UNUSED_ARG(to);
761 PJ_UNUSED_ARG(contact);
762
763 PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s",
764 (int)from->slen, from->ptr,
765 (is_typing?"is typing..":"has stopped typing")));
766}
767
768
769/**
770 * Call transfer request status.
771 */
772static void on_call_transfer_status(pjsua_call_id call_id,
773 int status_code,
774 const pj_str_t *status_text,
775 pj_bool_t final,
776 pj_bool_t *p_cont)
777{
778 PJ_LOG(3,(THIS_FILE, "Call %d: transfer status=%d (%.*s) %s",
779 call_id, status_code,
780 (int)status_text->slen, status_text->ptr,
781 (final ? "[final]" : "")));
782
783 if (status_code/100 == 2) {
784 PJ_LOG(3,(THIS_FILE,
785 "Call %d: call transfered successfully, disconnecting call",
786 call_id));
787 pjsua_call_hangup(call_id, PJSIP_SC_GONE, NULL, NULL);
788 *p_cont = PJ_FALSE;
789 }
790}
791
792
793/*
794 * Notification that call is being replaced.
795 */
796static void on_call_replaced(pjsua_call_id old_call_id,
797 pjsua_call_id new_call_id)
798{
799 pjsua_call_info old_ci, new_ci;
800
801 pjsua_call_get_info(old_call_id, &old_ci);
802 pjsua_call_get_info(new_call_id, &new_ci);
803
804 PJ_LOG(3,(THIS_FILE, "Call %d with %.*s is being replaced by "
805 "call %d with %.*s",
806 old_call_id,
807 (int)old_ci.remote_info.slen, old_ci.remote_info.ptr,
808 new_call_id,
809 (int)new_ci.remote_info.slen, new_ci.remote_info.ptr));
810}
811
812
813/*
814 * NAT type detection callback.
815 */
816static void on_nat_detect(const pj_stun_nat_detect_result *res)
817{
818 if (res->status != PJ_SUCCESS) {
819 pjsua_perror(THIS_FILE, "NAT detection failed", res->status);
820 } else {
821 PJ_LOG(3, (THIS_FILE, "NAT detected as %s", res->nat_type_name));
822 }
823}
824
825
826/*
827 * MWI indication
828 */
829static void on_mwi_info(pjsua_acc_id acc_id, pjsua_mwi_info *mwi_info)
830{
831 pj_str_t body;
832
833 PJ_LOG(3,(THIS_FILE, "Received MWI for acc %d:", acc_id));
834
835 if (mwi_info->rdata->msg_info.ctype) {
836 const pjsip_ctype_hdr *ctype = mwi_info->rdata->msg_info.ctype;
837
838 PJ_LOG(3,(THIS_FILE, " Content-Type: %.*s/%.*s",
839 (int)ctype->media.type.slen,
840 ctype->media.type.ptr,
841 (int)ctype->media.subtype.slen,
842 ctype->media.subtype.ptr));
843 }
844
845 if (!mwi_info->rdata->msg_info.msg->body) {
846 PJ_LOG(3,(THIS_FILE, " no message body"));
847 return;
848 }
849
850 body.ptr = mwi_info->rdata->msg_info.msg->body->data;
851 body.slen = mwi_info->rdata->msg_info.msg->body->len;
852
853 PJ_LOG(3,(THIS_FILE, " Body:\n%.*s", (int)body.slen, body.ptr));
854}
855
856
857/*
858 * Transport status notification
859 */
860static void on_transport_state(pjsip_transport *tp,
861 pjsip_transport_state state,
862 const pjsip_transport_state_info *info)
863{
864 char host_port[128];
865
866 pj_ansi_snprintf(host_port, sizeof(host_port), "[%.*s:%d]",
867 (int)tp->remote_name.host.slen,
868 tp->remote_name.host.ptr,
869 tp->remote_name.port);
870
871 switch (state) {
872 case PJSIP_TP_STATE_CONNECTED:
873 {
874 PJ_LOG(3,(THIS_FILE, "SIP %s transport is connected to %s",
875 tp->type_name, host_port));
876 }
877 break;
878
879 case PJSIP_TP_STATE_DISCONNECTED:
880 {
881 char buf[100];
882
883 snprintf(buf, sizeof(buf), "SIP %s transport is disconnected "
884 "from %s", tp->type_name, host_port);
885 pjsua_perror(THIS_FILE, buf, info->status);
886 }
887 break;
888
889 default:
890 break;
891 }
892
893#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
894
895 if (!pj_ansi_stricmp(tp->type_name, "tls") && info->ext_info &&
896 (state == PJSIP_TP_STATE_CONNECTED ||
897 ((pjsip_tls_state_info*)info->ext_info)->
898 ssl_sock_info->verify_status != PJ_SUCCESS))
899 {
900 pjsip_tls_state_info *tls_info = (pjsip_tls_state_info*)info->ext_info;
901 pj_ssl_sock_info *ssl_sock_info = tls_info->ssl_sock_info;
902 char buf[2048];
903 const char *verif_msgs[32];
904 unsigned verif_msg_cnt;
905
906 /* Dump server TLS cipher */
907 PJ_LOG(4,(THIS_FILE, "TLS cipher used: 0x%06X/%s",
908 ssl_sock_info->cipher,
909 pj_ssl_cipher_name(ssl_sock_info->cipher) ));
910
911 /* Dump server TLS certificate */
912 pj_ssl_cert_info_dump(ssl_sock_info->remote_cert_info, " ",
913 buf, sizeof(buf));
914 PJ_LOG(4,(THIS_FILE, "TLS cert info of %s:\n%s", host_port, buf));
915
916 /* Dump server TLS certificate verification result */
917 verif_msg_cnt = PJ_ARRAY_SIZE(verif_msgs);
918 pj_ssl_cert_get_verify_status_strings(ssl_sock_info->verify_status,
919 verif_msgs, &verif_msg_cnt);
920 PJ_LOG(3,(THIS_FILE, "TLS cert verification result of %s : %s",
921 host_port,
922 (verif_msg_cnt == 1? verif_msgs[0]:"")));
923 if (verif_msg_cnt > 1) {
924 unsigned i;
925 for (i = 0; i < verif_msg_cnt; ++i)
926 PJ_LOG(3,(THIS_FILE, "- %s", verif_msgs[i]));
927 }
928
929 if (ssl_sock_info->verify_status &&
930 !app_config.udp_cfg.tls_setting.verify_server)
931 {
932 PJ_LOG(3,(THIS_FILE, "PJSUA is configured to ignore TLS cert "
933 "verification errors"));
934 }
935 }
936
937#endif
938
939}
940
941/*
942 * Notification on ICE error.
943 */
944static void on_ice_transport_error(int index, pj_ice_strans_op op,
945 pj_status_t status, void *param)
946{
947 PJ_UNUSED_ARG(op);
948 PJ_UNUSED_ARG(param);
949 PJ_PERROR(1,(THIS_FILE, status,
950 "ICE keep alive failure for transport %d", index));
951}
952
953/*
954 * Notification on sound device operation.
955 */
956static pj_status_t on_snd_dev_operation(int operation)
957{
958 PJ_LOG(3,(THIS_FILE, "Turning sound device %s", (operation? "ON":"OFF")));
959 return PJ_SUCCESS;
960}
961
962/* Callback on media events */
963static void on_call_media_event(pjsua_call_id call_id,
964 unsigned med_idx,
965 pjmedia_event *event)
966{
967 char event_name[5];
968
969 PJ_LOG(5,(THIS_FILE, "Event %s",
970 pjmedia_fourcc_name(event->type, event_name)));
971
972#if PJSUA_HAS_VIDEO
973 if (event->type == PJMEDIA_EVENT_FMT_CHANGED) {
974 /* Adjust renderer window size to original video size */
975 pjsua_call_info ci;
976 pjsua_vid_win_id wid;
977 pjmedia_rect_size size;
978
979 pjsua_call_get_info(call_id, &ci);
980
981 if ((ci.media[med_idx].type == PJMEDIA_TYPE_VIDEO) &&
982 (ci.media[med_idx].dir & PJMEDIA_DIR_DECODING))
983 {
984 wid = ci.media[med_idx].stream.vid.win_in;
985 size = event->data.fmt_changed.new_fmt.det.vid.size;
986 pjsua_vid_win_set_size(wid, &size);
987 }
988
989 /* Re-arrange video windows */
990 arrange_window(PJSUA_INVALID_ID);
991 }
992#else
993 PJ_UNUSED_ARG(call_id);
994 PJ_UNUSED_ARG(med_idx);
995 PJ_UNUSED_ARG(event);
996#endif
997}
998
999#ifdef TRANSPORT_ADAPTER_SAMPLE
1000/*
1001 * This callback is called when media transport needs to be created.
1002 */
1003static pjmedia_transport* on_create_media_transport(pjsua_call_id call_id,
1004 unsigned media_idx,
1005 pjmedia_transport *base_tp,
1006 unsigned flags)
1007{
1008 pjmedia_transport *adapter;
1009 pj_status_t status;
1010
1011 /* Create the adapter */
1012 status = pjmedia_tp_adapter_create(pjsua_get_pjmedia_endpt(),
1013 NULL, base_tp,
1014 (flags & PJSUA_MED_TP_CLOSE_MEMBER),
1015 &adapter);
1016 if (status != PJ_SUCCESS) {
1017 PJ_PERROR(1,(THIS_FILE, status, "Error creating adapter"));
1018 return NULL;
1019 }
1020
1021 PJ_LOG(3,(THIS_FILE, "Media transport is created for call %d media %d",
1022 call_id, media_idx));
1023
1024 return adapter;
1025}
1026#endif
1027
1028/* Playfile done notification, set timer to hangup calls */
1029pj_status_t on_playfile_done(pjmedia_port *port, void *usr_data)
1030{
1031 pj_time_val delay;
1032
1033 PJ_UNUSED_ARG(port);
1034 PJ_UNUSED_ARG(usr_data);
1035
1036 /* Just rewind WAV when it is played outside of call */
1037 if (pjsua_call_get_count() == 0) {
1038 pjsua_player_set_pos(app_config.wav_id, 0);
1039 return PJ_SUCCESS;
1040 }
1041
1042 /* Timer is already active */
1043 if (app_config.auto_hangup_timer.id == 1)
1044 return PJ_SUCCESS;
1045
1046 app_config.auto_hangup_timer.id = 1;
1047 delay.sec = 0;
1048 delay.msec = 200; /* Give 200 ms before hangup */
1049 pjsip_endpt_schedule_timer(pjsua_get_pjsip_endpt(),
1050 &app_config.auto_hangup_timer,
1051 &delay);
1052
1053 return PJ_SUCCESS;
1054}
1055
1056/* Auto hangup timer callback */
1057static void hangup_timeout_callback(pj_timer_heap_t *timer_heap,
1058 struct pj_timer_entry *entry)
1059{
1060 PJ_UNUSED_ARG(timer_heap);
1061 PJ_UNUSED_ARG(entry);
1062
1063 app_config.auto_hangup_timer.id = 0;
1064 pjsua_call_hangup_all();
1065}
1066
1067/*
1068 * A simple registrar, invoked by default_mod_on_rx_request()
1069 */
1070static void simple_registrar(pjsip_rx_data *rdata)
1071{
1072 pjsip_tx_data *tdata;
1073 const pjsip_expires_hdr *exp;
1074 const pjsip_hdr *h;
1075 unsigned cnt = 0;
1076 pjsip_generic_string_hdr *srv;
1077 pj_status_t status;
1078
1079 status = pjsip_endpt_create_response(pjsua_get_pjsip_endpt(),
1080 rdata, 200, NULL, &tdata);
1081 if (status != PJ_SUCCESS)
1082 return;
1083
1084 exp = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
1085
1086 h = rdata->msg_info.msg->hdr.next;
1087 while (h != &rdata->msg_info.msg->hdr) {
1088 if (h->type == PJSIP_H_CONTACT) {
1089 const pjsip_contact_hdr *c = (const pjsip_contact_hdr*)h;
1090 int e = c->expires;
1091
1092 if (e < 0) {
1093 if (exp)
1094 e = exp->ivalue;
1095 else
1096 e = 3600;
1097 }
1098
1099 if (e > 0) {
1100 pjsip_contact_hdr *nc = pjsip_hdr_clone(tdata->pool, h);
1101 nc->expires = e;
1102 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)nc);
1103 ++cnt;
1104 }
1105 }
1106 h = h->next;
1107 }
1108
1109 srv = pjsip_generic_string_hdr_create(tdata->pool, NULL, NULL);
1110 srv->name = pj_str("Server");
1111 srv->hvalue = pj_str("pjsua simple registrar");
1112 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)srv);
1113
1114 pjsip_endpt_send_response2(pjsua_get_pjsip_endpt(),
1115 rdata, tdata, NULL, NULL);
1116}
1117
1118/*****************************************************************************
1119 * A simple module to handle otherwise unhandled request. We will register
1120 * this with the lowest priority.
1121 */
1122
1123/* Notification on incoming request */
1124static pj_bool_t default_mod_on_rx_request(pjsip_rx_data *rdata)
1125{
1126 pjsip_tx_data *tdata;
1127 pjsip_status_code status_code;
1128 pj_status_t status;
1129
1130 /* Don't respond to ACK! */
1131 if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
1132 &pjsip_ack_method) == 0)
1133 return PJ_TRUE;
1134
1135 /* Simple registrar */
1136 if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
1137 &pjsip_register_method) == 0)
1138 {
1139 simple_registrar(rdata);
1140 return PJ_TRUE;
1141 }
1142
1143 /* Create basic response. */
1144 if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
1145 &pjsip_notify_method) == 0)
1146 {
1147 /* Unsolicited NOTIFY's, send with Bad Request */
1148 status_code = PJSIP_SC_BAD_REQUEST;
1149 } else {
1150 /* Probably unknown method */
1151 status_code = PJSIP_SC_METHOD_NOT_ALLOWED;
1152 }
1153 status = pjsip_endpt_create_response(pjsua_get_pjsip_endpt(),
1154 rdata, status_code,
1155 NULL, &tdata);
1156 if (status != PJ_SUCCESS) {
1157 pjsua_perror(THIS_FILE, "Unable to create response", status);
1158 return PJ_TRUE;
1159 }
1160
1161 /* Add Allow if we're responding with 405 */
1162 if (status_code == PJSIP_SC_METHOD_NOT_ALLOWED) {
1163 const pjsip_hdr *cap_hdr;
1164 cap_hdr = pjsip_endpt_get_capability(pjsua_get_pjsip_endpt(),
1165 PJSIP_H_ALLOW, NULL);
1166 if (cap_hdr) {
1167 pjsip_msg_add_hdr(tdata->msg, pjsip_hdr_clone(tdata->pool,
1168 cap_hdr));
1169 }
1170 }
1171
1172 /* Add User-Agent header */
1173 {
1174 pj_str_t user_agent;
1175 char tmp[80];
1176 const pj_str_t USER_AGENT = { "User-Agent", 10};
1177 pjsip_hdr *h;
1178
1179 pj_ansi_snprintf(tmp, sizeof(tmp), "PJSUA v%s/%s",
1180 pj_get_version(), PJ_OS_NAME);
1181 pj_strdup2_with_null(tdata->pool, &user_agent, tmp);
1182
1183 h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool,
1184 &USER_AGENT,
1185 &user_agent);
1186 pjsip_msg_add_hdr(tdata->msg, h);
1187 }
1188
1189 pjsip_endpt_send_response2(pjsua_get_pjsip_endpt(), rdata, tdata,
1190 NULL, NULL);
1191
1192 return PJ_TRUE;
1193}
1194
1195/* The module instance. */
1196static pjsip_module mod_default_handler =
1197{
1198 NULL, NULL, /* prev, next. */
1199 { "mod-default-handler", 19 }, /* Name. */
1200 -1, /* Id */
1201 PJSIP_MOD_PRIORITY_APPLICATION+99, /* Priority */
1202 NULL, /* load() */
1203 NULL, /* start() */
1204 NULL, /* stop() */
1205 NULL, /* unload() */
1206 &default_mod_on_rx_request, /* on_rx_request() */
1207 NULL, /* on_rx_response() */
1208 NULL, /* on_tx_request. */
1209 NULL, /* on_tx_response() */
1210 NULL, /* on_tsx_state() */
1211
1212};
1213
1214/** CLI callback **/
1215
1216/* Called on CLI (re)started, e.g: initial start, after iOS bg */
1217void cli_on_started(pj_status_t status)
1218{
1219 /* Notify app */
1220 if (app_cfg.on_started) {
1221 if (status == PJ_SUCCESS) {
1222 char info[128];
1223 cli_get_info(info, sizeof(info));
1224 if (app_cfg.on_started) {
1225 (*app_cfg.on_started)(status, info);
1226 }
1227 } else {
1228 if (app_cfg.on_started) {
1229 (*app_cfg.on_started)(status, NULL);
1230 }
1231 }
1232 }
1233}
1234
1235/* Called on CLI quit */
1236void cli_on_stopped(pj_bool_t restart, int argc, char* argv[])
1237{
1238 /* Notify app */
1239 if (app_cfg.on_stopped)
1240 (*app_cfg.on_stopped)(restart, argc, argv);
1241}
1242
1243
1244/* Called on pjsua legacy quit */
1245void legacy_on_stopped(pj_bool_t restart)
1246{
1247 /* Notify app */
1248 if (app_cfg.on_stopped)
1249 (*app_cfg.on_stopped)(restart, 1, NULL);
1250}
1251
1252/*****************************************************************************
1253 * Public API
1254 */
1255
1256int stdout_refresh_proc(void *arg)
1257{
1258 extern char *stdout_refresh_text;
1259
1260 PJ_UNUSED_ARG(arg);
1261
1262 /* Set thread to lowest priority so that it doesn't clobber
1263 * stdout output
1264 */
1265 pj_thread_set_prio(pj_thread_this(),
1266 pj_thread_get_prio_min(pj_thread_this()));
1267
1268 while (!stdout_refresh_quit) {
1269 pj_thread_sleep(stdout_refresh * 1000);
1270 puts(stdout_refresh_text);
1271 fflush(stdout);
1272 }
1273
1274 return 0;
1275}
1276
1277static pj_status_t app_init()
1278{
1279 pjsua_transport_id transport_id = -1;
1280 pjsua_transport_config tcp_cfg;
1281 unsigned i;
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001282 pj_pool_t *tmp_pool;
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001283 pj_status_t status;
1284
1285 /** Create pjsua **/
1286 status = pjsua_create();
1287 if (status != PJ_SUCCESS)
1288 return status;
1289
1290 /* Create pool for application */
1291 app_config.pool = pjsua_pool_create("pjsua-app", 1000, 1000);
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001292 tmp_pool = pjsua_pool_create("tmp-pjsua", 1000, 1000);;
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001293
1294 /* Init CLI & its FE settings */
1295 if (!app_running) {
1296 pj_cli_cfg_default(&app_config.cli_cfg.cfg);
1297 pj_cli_telnet_cfg_default(&app_config.cli_cfg.telnet_cfg);
1298 pj_cli_console_cfg_default(&app_config.cli_cfg.console_cfg);
1299 app_config.cli_cfg.telnet_cfg.on_started = cli_on_started;
1300 }
1301
1302 /** Parse args **/
1303 status = load_config(app_cfg.argc, app_cfg.argv, &uri_arg);
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001304 if (status != PJ_SUCCESS) {
1305 pj_pool_release(tmp_pool);
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001306 return status;
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001307 }
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001308
1309 /* Initialize application callbacks */
1310 app_config.cfg.cb.on_call_state = &on_call_state;
1311 app_config.cfg.cb.on_call_media_state = &on_call_media_state;
1312 app_config.cfg.cb.on_incoming_call = &on_incoming_call;
1313 app_config.cfg.cb.on_call_tsx_state = &on_call_tsx_state;
1314 app_config.cfg.cb.on_dtmf_digit = &call_on_dtmf_callback;
1315 app_config.cfg.cb.on_call_redirected = &call_on_redirected;
1316 app_config.cfg.cb.on_reg_state = &on_reg_state;
1317 app_config.cfg.cb.on_incoming_subscribe = &on_incoming_subscribe;
1318 app_config.cfg.cb.on_buddy_state = &on_buddy_state;
1319 app_config.cfg.cb.on_buddy_evsub_state = &on_buddy_evsub_state;
1320 app_config.cfg.cb.on_pager = &on_pager;
1321 app_config.cfg.cb.on_typing = &on_typing;
1322 app_config.cfg.cb.on_call_transfer_status = &on_call_transfer_status;
1323 app_config.cfg.cb.on_call_replaced = &on_call_replaced;
1324 app_config.cfg.cb.on_nat_detect = &on_nat_detect;
1325 app_config.cfg.cb.on_mwi_info = &on_mwi_info;
1326 app_config.cfg.cb.on_transport_state = &on_transport_state;
1327 app_config.cfg.cb.on_ice_transport_error = &on_ice_transport_error;
1328 app_config.cfg.cb.on_snd_dev_operation = &on_snd_dev_operation;
1329 app_config.cfg.cb.on_call_media_event = &on_call_media_event;
1330#ifdef TRANSPORT_ADAPTER_SAMPLE
1331 app_config.cfg.cb.on_create_media_transport = &on_create_media_transport;
1332#endif
1333
1334 /* Set sound device latency */
1335 if (app_config.capture_lat > 0)
1336 app_config.media_cfg.snd_rec_latency = app_config.capture_lat;
1337 if (app_config.playback_lat)
1338 app_config.media_cfg.snd_play_latency = app_config.playback_lat;
1339
1340 if (app_cfg.on_config_init)
1341 (*app_cfg.on_config_init)(&app_config);
1342
1343 /* Initialize pjsua */
1344 status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
1345 &app_config.media_cfg);
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001346 if (status != PJ_SUCCESS) {
1347 pj_pool_release(tmp_pool);
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001348 return status;
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001349 }
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001350
1351 /* Initialize our module to handle otherwise unhandled request */
1352 status = pjsip_endpt_register_module(pjsua_get_pjsip_endpt(),
1353 &mod_default_handler);
1354 if (status != PJ_SUCCESS)
1355 return status;
1356
1357#ifdef STEREO_DEMO
1358 stereo_demo();
1359#endif
1360
1361 /* Initialize calls data */
1362 for (i=0; i<PJ_ARRAY_SIZE(app_config.call_data); ++i) {
1363 app_config.call_data[i].timer.id = PJSUA_INVALID_ID;
1364 app_config.call_data[i].timer.cb = &call_timeout_callback;
1365 }
1366
1367 /* Optionally registers WAV file */
1368 for (i=0; i<app_config.wav_count; ++i) {
1369 pjsua_player_id wav_id;
1370 unsigned play_options = 0;
1371
1372 if (app_config.auto_play_hangup)
1373 play_options |= PJMEDIA_FILE_NO_LOOP;
1374
1375 status = pjsua_player_create(&app_config.wav_files[i], play_options,
1376 &wav_id);
1377 if (status != PJ_SUCCESS)
1378 goto on_error;
1379
1380 if (app_config.wav_id == PJSUA_INVALID_ID) {
1381 app_config.wav_id = wav_id;
1382 app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id);
1383 if (app_config.auto_play_hangup) {
1384 pjmedia_port *port;
1385
1386 pjsua_player_get_port(app_config.wav_id, &port);
1387 status = pjmedia_wav_player_set_eof_cb(port, NULL,
1388 &on_playfile_done);
1389 if (status != PJ_SUCCESS)
1390 goto on_error;
1391
1392 pj_timer_entry_init(&app_config.auto_hangup_timer, 0, NULL,
1393 &hangup_timeout_callback);
1394 }
1395 }
1396 }
1397
1398 /* Optionally registers tone players */
1399 for (i=0; i<app_config.tone_count; ++i) {
1400 pjmedia_port *tport;
1401 char name[80];
1402 pj_str_t label;
1403 pj_status_t status;
1404
1405 pj_ansi_snprintf(name, sizeof(name), "tone-%d,%d",
1406 app_config.tones[i].freq1,
1407 app_config.tones[i].freq2);
1408 label = pj_str(name);
1409 status = pjmedia_tonegen_create2(app_config.pool, &label,
1410 8000, 1, 160, 16,
1411 PJMEDIA_TONEGEN_LOOP, &tport);
1412 if (status != PJ_SUCCESS) {
1413 pjsua_perror(THIS_FILE, "Unable to create tone generator", status);
1414 goto on_error;
1415 }
1416
1417 status = pjsua_conf_add_port(app_config.pool, tport,
1418 &app_config.tone_slots[i]);
1419 pj_assert(status == PJ_SUCCESS);
1420
1421 status = pjmedia_tonegen_play(tport, 1, &app_config.tones[i], 0);
1422 pj_assert(status == PJ_SUCCESS);
1423 }
1424
1425 /* Optionally create recorder file, if any. */
1426 if (app_config.rec_file.slen) {
1427 status = pjsua_recorder_create(&app_config.rec_file, 0, NULL, 0, 0,
1428 &app_config.rec_id);
1429 if (status != PJ_SUCCESS)
1430 goto on_error;
1431
1432 app_config.rec_port = pjsua_recorder_get_conf_port(app_config.rec_id);
1433 }
1434
1435 pj_memcpy(&tcp_cfg, &app_config.udp_cfg, sizeof(tcp_cfg));
1436
1437 /* Create ringback tones */
1438 if (app_config.no_tones == PJ_FALSE) {
1439 unsigned i, samples_per_frame;
1440 pjmedia_tone_desc tone[RING_CNT+RINGBACK_CNT];
1441 pj_str_t name;
1442
1443 samples_per_frame = app_config.media_cfg.audio_frame_ptime *
1444 app_config.media_cfg.clock_rate *
1445 app_config.media_cfg.channel_count / 1000;
1446
1447 /* Ringback tone (call is ringing) */
1448 name = pj_str("ringback");
1449 status = pjmedia_tonegen_create2(app_config.pool, &name,
1450 app_config.media_cfg.clock_rate,
1451 app_config.media_cfg.channel_count,
1452 samples_per_frame,
1453 16, PJMEDIA_TONEGEN_LOOP,
1454 &app_config.ringback_port);
1455 if (status != PJ_SUCCESS)
1456 goto on_error;
1457
1458 pj_bzero(&tone, sizeof(tone));
1459 for (i=0; i<RINGBACK_CNT; ++i) {
1460 tone[i].freq1 = RINGBACK_FREQ1;
1461 tone[i].freq2 = RINGBACK_FREQ2;
1462 tone[i].on_msec = RINGBACK_ON;
1463 tone[i].off_msec = RINGBACK_OFF;
1464 }
1465 tone[RINGBACK_CNT-1].off_msec = RINGBACK_INTERVAL;
1466
1467 pjmedia_tonegen_play(app_config.ringback_port, RINGBACK_CNT, tone,
1468 PJMEDIA_TONEGEN_LOOP);
1469
1470
1471 status = pjsua_conf_add_port(app_config.pool, app_config.ringback_port,
1472 &app_config.ringback_slot);
1473 if (status != PJ_SUCCESS)
1474 goto on_error;
1475
1476 /* Ring (to alert incoming call) */
1477 name = pj_str("ring");
1478 status = pjmedia_tonegen_create2(app_config.pool, &name,
1479 app_config.media_cfg.clock_rate,
1480 app_config.media_cfg.channel_count,
1481 samples_per_frame,
1482 16, PJMEDIA_TONEGEN_LOOP,
1483 &app_config.ring_port);
1484 if (status != PJ_SUCCESS)
1485 goto on_error;
1486
1487 for (i=0; i<RING_CNT; ++i) {
1488 tone[i].freq1 = RING_FREQ1;
1489 tone[i].freq2 = RING_FREQ2;
1490 tone[i].on_msec = RING_ON;
1491 tone[i].off_msec = RING_OFF;
1492 }
1493 tone[RING_CNT-1].off_msec = RING_INTERVAL;
1494
1495 pjmedia_tonegen_play(app_config.ring_port, RING_CNT,
1496 tone, PJMEDIA_TONEGEN_LOOP);
1497
1498 status = pjsua_conf_add_port(app_config.pool, app_config.ring_port,
1499 &app_config.ring_slot);
1500 if (status != PJ_SUCCESS)
1501 goto on_error;
1502
1503 }
1504
1505 /* Create AVI player virtual devices */
1506 if (app_config.avi_cnt) {
1507#if PJMEDIA_HAS_VIDEO && PJMEDIA_VIDEO_DEV_HAS_AVI
1508 pjmedia_vid_dev_factory *avi_factory;
1509
1510 status = pjmedia_avi_dev_create_factory(pjsua_get_pool_factory(),
1511 app_config.avi_cnt,
1512 &avi_factory);
1513 if (status != PJ_SUCCESS) {
1514 PJ_PERROR(1,(THIS_FILE, status, "Error creating AVI factory"));
1515 goto on_error;
1516 }
1517
1518 for (i=0; i<app_config.avi_cnt; ++i) {
1519 pjmedia_avi_dev_param avdp;
1520 pjmedia_vid_dev_index avid;
1521 unsigned strm_idx, strm_cnt;
1522
1523 app_config.avi[i].dev_id = PJMEDIA_VID_INVALID_DEV;
1524 app_config.avi[i].slot = PJSUA_INVALID_ID;
1525
1526 pjmedia_avi_dev_param_default(&avdp);
1527 avdp.path = app_config.avi[i].path;
1528
1529 status = pjmedia_avi_dev_alloc(avi_factory, &avdp, &avid);
1530 if (status != PJ_SUCCESS) {
1531 PJ_PERROR(1,(THIS_FILE, status,
1532 "Error creating AVI player for %.*s",
1533 (int)avdp.path.slen, avdp.path.ptr));
1534 goto on_error;
1535 }
1536
1537 PJ_LOG(4,(THIS_FILE, "AVI player %.*s created, dev_id=%d",
1538 (int)avdp.title.slen, avdp.title.ptr, avid));
1539
1540 app_config.avi[i].dev_id = avid;
1541 if (app_config.avi_def_idx == PJSUA_INVALID_ID)
1542 app_config.avi_def_idx = i;
1543
1544 strm_cnt = pjmedia_avi_streams_get_num_streams(avdp.avi_streams);
1545 for (strm_idx=0; strm_idx<strm_cnt; ++strm_idx) {
1546 pjmedia_port *aud;
1547 pjmedia_format *fmt;
1548 pjsua_conf_port_id slot;
1549 char fmt_name[5];
1550
1551 aud = pjmedia_avi_streams_get_stream(avdp.avi_streams,
1552 strm_idx);
1553 fmt = &aud->info.fmt;
1554
1555 pjmedia_fourcc_name(fmt->id, fmt_name);
1556
1557 if (fmt->id == PJMEDIA_FORMAT_PCM) {
1558 status = pjsua_conf_add_port(app_config.pool, aud,
1559 &slot);
1560 if (status == PJ_SUCCESS) {
1561 PJ_LOG(4,(THIS_FILE,
1562 "AVI %.*s: audio added to slot %d",
1563 (int)avdp.title.slen, avdp.title.ptr,
1564 slot));
1565 app_config.avi[i].slot = slot;
1566 }
1567 } else {
1568 PJ_LOG(4,(THIS_FILE,
1569 "AVI %.*s: audio ignored, format=%s",
1570 (int)avdp.title.slen, avdp.title.ptr,
1571 fmt_name));
1572 }
1573 }
1574 }
1575#else
1576 PJ_LOG(2,(THIS_FILE,
1577 "Warning: --play-avi is ignored because AVI is disabled"));
1578#endif /* PJMEDIA_VIDEO_DEV_HAS_AVI */
1579 }
1580
1581 /* Add UDP transport unless it's disabled. */
1582 if (!app_config.no_udp) {
1583 pjsua_acc_id aid;
1584 pjsip_transport_type_e type = PJSIP_TRANSPORT_UDP;
1585
1586 status = pjsua_transport_create(type,
1587 &app_config.udp_cfg,
1588 &transport_id);
1589 if (status != PJ_SUCCESS)
1590 goto on_error;
1591
1592 /* Add local account */
1593 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1594 if (PJMEDIA_HAS_VIDEO) {
1595 pjsua_acc_config acc_cfg;
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001596 pjsua_acc_get_config(aid, tmp_pool, &acc_cfg);
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001597 app_config_init_video(&acc_cfg);
1598 pjsua_acc_modify(aid, &acc_cfg);
1599 }
1600 //pjsua_acc_set_transport(aid, transport_id);
1601 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1602
1603 if (app_config.udp_cfg.port == 0) {
1604 pjsua_transport_info ti;
1605 pj_sockaddr_in *a;
1606
1607 pjsua_transport_get_info(transport_id, &ti);
1608 a = (pj_sockaddr_in*)&ti.local_addr;
1609
1610 tcp_cfg.port = pj_ntohs(a->sin_port);
1611 }
1612 }
1613
1614 /* Add UDP IPv6 transport unless it's disabled. */
1615 if (!app_config.no_udp && app_config.ipv6) {
1616 pjsua_acc_id aid;
1617 pjsip_transport_type_e type = PJSIP_TRANSPORT_UDP6;
1618 pjsua_transport_config udp_cfg;
1619
1620 udp_cfg = app_config.udp_cfg;
1621 if (udp_cfg.port == 0)
1622 udp_cfg.port = 5060;
1623 else
1624 udp_cfg.port += 10;
1625 status = pjsua_transport_create(type,
1626 &udp_cfg,
1627 &transport_id);
1628 if (status != PJ_SUCCESS)
1629 goto on_error;
1630
1631 /* Add local account */
1632 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1633 if (PJMEDIA_HAS_VIDEO) {
1634 pjsua_acc_config acc_cfg;
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001635 pjsua_acc_get_config(aid, tmp_pool, &acc_cfg);
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001636 app_config_init_video(&acc_cfg);
1637 if (app_config.ipv6)
1638 acc_cfg.ipv6_media_use = PJSUA_IPV6_ENABLED;
1639 pjsua_acc_modify(aid, &acc_cfg);
1640 }
1641 //pjsua_acc_set_transport(aid, transport_id);
1642 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1643
1644 if (app_config.udp_cfg.port == 0) {
1645 pjsua_transport_info ti;
1646 pj_sockaddr_in *a;
1647
1648 pjsua_transport_get_info(transport_id, &ti);
1649 a = (pj_sockaddr_in*)&ti.local_addr;
1650
1651 tcp_cfg.port = pj_ntohs(a->sin_port);
1652 }
1653 }
1654
1655 /* Add TCP transport unless it's disabled */
1656 if (!app_config.no_tcp) {
1657 pjsua_acc_id aid;
1658
1659 status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
1660 &tcp_cfg,
1661 &transport_id);
1662 if (status != PJ_SUCCESS)
1663 goto on_error;
1664
1665 /* Add local account */
1666 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1667 if (PJMEDIA_HAS_VIDEO) {
1668 pjsua_acc_config acc_cfg;
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001669 pjsua_acc_get_config(aid, tmp_pool, &acc_cfg);
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001670 app_config_init_video(&acc_cfg);
1671 pjsua_acc_modify(aid, &acc_cfg);
1672 }
1673 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1674
1675 }
1676
1677 /* Add TCP IPv6 transport unless it's disabled. */
1678 if (!app_config.no_tcp && app_config.ipv6) {
1679 pjsua_acc_id aid;
1680 pjsip_transport_type_e type = PJSIP_TRANSPORT_TCP6;
1681
1682 tcp_cfg.port += 10;
1683
1684 status = pjsua_transport_create(type,
1685 &tcp_cfg,
1686 &transport_id);
1687 if (status != PJ_SUCCESS)
1688 goto on_error;
1689
1690 /* Add local account */
1691 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1692 if (PJMEDIA_HAS_VIDEO) {
1693 pjsua_acc_config acc_cfg;
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001694 pjsua_acc_get_config(aid, tmp_pool, &acc_cfg);
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001695 app_config_init_video(&acc_cfg);
1696 if (app_config.ipv6)
1697 acc_cfg.ipv6_media_use = PJSUA_IPV6_ENABLED;
1698 pjsua_acc_modify(aid, &acc_cfg);
1699 }
1700 //pjsua_acc_set_transport(aid, transport_id);
1701 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1702 }
1703
1704
1705#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
1706 /* Add TLS transport when application wants one */
1707 if (app_config.use_tls) {
1708
1709 pjsua_acc_id acc_id;
1710
1711 /* Copy the QoS settings */
1712 tcp_cfg.tls_setting.qos_type = tcp_cfg.qos_type;
1713 pj_memcpy(&tcp_cfg.tls_setting.qos_params, &tcp_cfg.qos_params,
1714 sizeof(tcp_cfg.qos_params));
1715
1716 /* Set TLS port as TCP port+1 */
1717 tcp_cfg.port++;
1718 status = pjsua_transport_create(PJSIP_TRANSPORT_TLS,
1719 &tcp_cfg,
1720 &transport_id);
1721 tcp_cfg.port--;
1722 if (status != PJ_SUCCESS)
1723 goto on_error;
1724
1725 /* Add local account */
1726 pjsua_acc_add_local(transport_id, PJ_FALSE, &acc_id);
1727 if (PJMEDIA_HAS_VIDEO) {
1728 pjsua_acc_config acc_cfg;
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001729 pjsua_acc_get_config(acc_id, tmp_pool, &acc_cfg);
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001730 app_config_init_video(&acc_cfg);
1731 pjsua_acc_modify(acc_id, &acc_cfg);
1732 }
1733 pjsua_acc_set_online_status(acc_id, PJ_TRUE);
1734 }
1735
1736 /* Add TLS IPv6 transport unless it's disabled. */
1737 if (app_config.use_tls && app_config.ipv6) {
1738 pjsua_acc_id aid;
1739 pjsip_transport_type_e type = PJSIP_TRANSPORT_TLS6;
1740
1741 tcp_cfg.port += 10;
1742
1743 status = pjsua_transport_create(type,
1744 &tcp_cfg,
1745 &transport_id);
1746 if (status != PJ_SUCCESS)
1747 goto on_error;
1748
1749 /* Add local account */
1750 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1751 if (PJMEDIA_HAS_VIDEO) {
1752 pjsua_acc_config acc_cfg;
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001753 pjsua_acc_get_config(aid, tmp_pool, &acc_cfg);
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001754 app_config_init_video(&acc_cfg);
1755 if (app_config.ipv6)
1756 acc_cfg.ipv6_media_use = PJSUA_IPV6_ENABLED;
1757 pjsua_acc_modify(aid, &acc_cfg);
1758 }
1759 //pjsua_acc_set_transport(aid, transport_id);
1760 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1761 }
1762
1763#endif
1764
1765 if (transport_id == -1) {
1766 PJ_LOG(1,(THIS_FILE, "Error: no transport is configured"));
1767 status = -1;
1768 goto on_error;
1769 }
1770
1771
1772 /* Add accounts */
1773 for (i=0; i<app_config.acc_cnt; ++i) {
1774 app_config.acc_cfg[i].rtp_cfg = app_config.rtp_cfg;
1775 app_config.acc_cfg[i].reg_retry_interval = 300;
1776 app_config.acc_cfg[i].reg_first_retry_interval = 60;
1777
1778 app_config_init_video(&app_config.acc_cfg[i]);
1779
1780 status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL);
1781 if (status != PJ_SUCCESS)
1782 goto on_error;
1783 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1784 }
1785
1786 /* Add buddies */
1787 for (i=0; i<app_config.buddy_cnt; ++i) {
1788 status = pjsua_buddy_add(&app_config.buddy_cfg[i], NULL);
1789 if (status != PJ_SUCCESS) {
1790 PJ_PERROR(1,(THIS_FILE, status, "Error adding buddy"));
1791 goto on_error;
1792 }
1793 }
1794
1795 /* Optionally disable some codec */
1796 for (i=0; i<app_config.codec_dis_cnt; ++i) {
1797 pjsua_codec_set_priority(&app_config.codec_dis[i],
1798 PJMEDIA_CODEC_PRIO_DISABLED);
1799#if PJSUA_HAS_VIDEO
1800 pjsua_vid_codec_set_priority(&app_config.codec_dis[i],
1801 PJMEDIA_CODEC_PRIO_DISABLED);
1802#endif
1803 }
1804
1805 /* Optionally set codec orders */
1806 for (i=0; i<app_config.codec_cnt; ++i) {
1807 pjsua_codec_set_priority(&app_config.codec_arg[i],
1808 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
1809#if PJSUA_HAS_VIDEO
1810 pjsua_vid_codec_set_priority(&app_config.codec_arg[i],
1811 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
1812#endif
1813 }
1814
1815 /* Use null sound device? */
1816#ifndef STEREO_DEMO
1817 if (app_config.null_audio) {
1818 status = pjsua_set_null_snd_dev();
1819 if (status != PJ_SUCCESS)
1820 return status;
1821 }
1822#endif
1823
1824 if (app_config.capture_dev != PJSUA_INVALID_ID ||
1825 app_config.playback_dev != PJSUA_INVALID_ID)
1826 {
1827 status = pjsua_set_snd_dev(app_config.capture_dev,
1828 app_config.playback_dev);
1829 if (status != PJ_SUCCESS)
1830 goto on_error;
1831 }
1832
1833 /* Init call setting */
1834 pjsua_call_setting_default(&call_opt);
1835 call_opt.aud_cnt = app_config.aud_cnt;
1836 call_opt.vid_cnt = app_config.vid.vid_cnt;
1837
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001838 pj_pool_release(tmp_pool);
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001839 return PJ_SUCCESS;
1840
1841on_error:
Alexandre Lision94f06ba2013-12-09 16:28:33 -05001842 pj_pool_release(tmp_pool);
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001843 app_destroy();
1844 return status;
1845}
1846
1847pj_status_t pjsua_app_init(const pjsua_app_cfg_t *cfg)
1848{
1849 pj_status_t status;
1850 pj_memcpy(&app_cfg, cfg, sizeof(app_cfg));
1851
1852 status = app_init();
1853 if (status != PJ_SUCCESS)
1854 return status;
1855
1856 /* Init CLI if configured */
1857 if (app_config.use_cli) {
1858 status = cli_init();
1859 }
1860 return status;
1861}
1862
1863pj_status_t pjsua_app_run(pj_bool_t wait_telnet_cli)
1864{
1865 pj_thread_t *stdout_refresh_thread = NULL;
1866 pj_status_t status;
1867
1868 /* Start console refresh thread */
1869 if (stdout_refresh > 0) {
1870 pj_thread_create(app_config.pool, "stdout", &stdout_refresh_proc,
1871 NULL, 0, 0, &stdout_refresh_thread);
1872 }
1873
1874 status = pjsua_start();
1875 if (status != PJ_SUCCESS)
1876 goto on_return;
1877
1878 if (app_config.use_cli && (app_config.cli_cfg.cli_fe & CLI_FE_TELNET)) {
1879 char info[128];
1880 cli_get_info(info, sizeof(info));
1881 if (app_cfg.on_started) {
1882 (*app_cfg.on_started)(status, info);
1883 }
1884 } else {
1885 if (app_cfg.on_started) {
1886 (*app_cfg.on_started)(status, "Ready");
1887 }
1888 }
1889
1890 /* If user specifies URI to call, then call the URI */
1891 if (uri_arg.slen) {
1892 pjsua_call_setting_default(&call_opt);
1893 call_opt.aud_cnt = app_config.aud_cnt;
1894 call_opt.vid_cnt = app_config.vid.vid_cnt;
1895
1896 pjsua_call_make_call(current_acc, &uri_arg, &call_opt, NULL,
1897 NULL, NULL);
1898 }
1899
1900 app_running = PJ_TRUE;
1901
1902 if (app_config.use_cli)
1903 cli_main(wait_telnet_cli);
1904 else
1905 legacy_main();
1906
1907 status = PJ_SUCCESS;
1908
1909on_return:
1910 if (stdout_refresh_thread) {
1911 stdout_refresh_quit = PJ_TRUE;
1912 pj_thread_join(stdout_refresh_thread);
1913 pj_thread_destroy(stdout_refresh_thread);
1914 stdout_refresh_quit = PJ_FALSE;
1915 }
1916 return status;
1917}
1918
1919static pj_status_t app_destroy()
1920{
1921 pj_status_t status = PJ_SUCCESS;
1922 unsigned i;
1923 pj_bool_t use_cli = PJ_FALSE;
1924 int cli_fe = 0;
1925 pj_uint16_t cli_telnet_port = 0;
1926
1927#ifdef STEREO_DEMO
1928 if (app_config.snd) {
1929 pjmedia_snd_port_destroy(app_config.snd);
1930 app_config.snd = NULL;
1931 }
1932 if (app_config.sc_ch1) {
1933 pjsua_conf_remove_port(app_config.sc_ch1_slot);
1934 app_config.sc_ch1_slot = PJSUA_INVALID_ID;
1935 pjmedia_port_destroy(app_config.sc_ch1);
1936 app_config.sc_ch1 = NULL;
1937 }
1938 if (app_config.sc) {
1939 pjmedia_port_destroy(app_config.sc);
1940 app_config.sc = NULL;
1941 }
1942#endif
1943
1944 /* Close avi devs and ports */
1945 for (i=0; i<app_config.avi_cnt; ++i) {
1946 if (app_config.avi[i].slot != PJSUA_INVALID_ID)
1947 pjsua_conf_remove_port(app_config.avi[i].slot);
1948#if PJMEDIA_HAS_VIDEO && PJMEDIA_VIDEO_DEV_HAS_AVI
1949 if (app_config.avi[i].dev_id != PJMEDIA_VID_INVALID_DEV)
1950 pjmedia_avi_dev_free(app_config.avi[i].dev_id);
1951#endif
1952 }
1953
1954 /* Close ringback port */
1955 if (app_config.ringback_port &&
1956 app_config.ringback_slot != PJSUA_INVALID_ID)
1957 {
1958 pjsua_conf_remove_port(app_config.ringback_slot);
1959 app_config.ringback_slot = PJSUA_INVALID_ID;
1960 pjmedia_port_destroy(app_config.ringback_port);
1961 app_config.ringback_port = NULL;
1962 }
1963
1964 /* Close ring port */
1965 if (app_config.ring_port && app_config.ring_slot != PJSUA_INVALID_ID) {
1966 pjsua_conf_remove_port(app_config.ring_slot);
1967 app_config.ring_slot = PJSUA_INVALID_ID;
1968 pjmedia_port_destroy(app_config.ring_port);
1969 app_config.ring_port = NULL;
1970 }
1971
1972 /* Close tone generators */
1973 for (i=0; i<app_config.tone_count; ++i) {
1974 pjsua_conf_remove_port(app_config.tone_slots[i]);
1975 }
1976
1977 if (app_config.pool) {
1978 pj_pool_release(app_config.pool);
1979 app_config.pool = NULL;
1980 }
1981
1982 status = pjsua_destroy();
1983
1984 if (app_config.use_cli) {
1985 use_cli = app_config.use_cli;
1986 cli_fe = app_config.cli_cfg.cli_fe;
1987 cli_telnet_port = app_config.cli_cfg.telnet_cfg.port;
1988 }
1989
1990 /* Reset config */
1991 pj_bzero(&app_config, sizeof(app_config));
1992
1993 if (use_cli) {
1994 app_config.use_cli = use_cli;
1995 app_config.cli_cfg.cli_fe = cli_fe;
1996 app_config.cli_cfg.telnet_cfg.port = cli_telnet_port;
1997 }
1998
1999 return status;
2000}
2001
2002pj_status_t pjsua_app_destroy()
2003{
2004 pj_status_t status;
2005
2006 status = app_destroy();
2007
2008 if (app_config.use_cli) {
2009 cli_destroy();
2010 }
2011
2012 return status;
2013}
2014
2015/** ======================= **/
2016
2017#ifdef STEREO_DEMO
2018/*
2019 * In this stereo demo, we open the sound device in stereo mode and
2020 * arrange the attachment to the PJSUA-LIB conference bridge as such
2021 * so that channel0/left channel of the sound device corresponds to
2022 * slot 0 in the bridge, and channel1/right channel of the sound
2023 * device corresponds to slot 1 in the bridge. Then user can independently
2024 * feed different media to/from the speakers/microphones channels, by
2025 * connecting them to slot 0 or 1 respectively.
2026 *
2027 * Here's how the connection looks like:
2028 *
2029 +-----------+ stereo +-----------------+ 2x mono +-----------+
2030 | AUDIO DEV |<------>| SPLITCOMB left|<------->|#0 BRIDGE |
2031 +-----------+ | right|<------->|#1 |
2032 +-----------------+ +-----------+
2033 */
2034static void stereo_demo()
2035{
2036 pjmedia_port *conf;
2037 pj_status_t status;
2038
2039 /* Disable existing sound device */
2040 conf = pjsua_set_no_snd_dev();
2041
2042 /* Create stereo-mono splitter/combiner */
2043 status = pjmedia_splitcomb_create(app_config.pool,
2044 conf->info.clock_rate /* clock rate */,
2045 2 /* stereo */,
2046 2 * conf->info.samples_per_frame,
2047 conf->info.bits_per_sample,
2048 0 /* options */,
2049 &app_config.sc);
2050 pj_assert(status == PJ_SUCCESS);
2051
2052 /* Connect channel0 (left channel?) to conference port slot0 */
2053 status = pjmedia_splitcomb_set_channel(app_config.sc, 0 /* ch0 */,
2054 0 /*options*/,
2055 conf);
2056 pj_assert(status == PJ_SUCCESS);
2057
2058 /* Create reverse channel for channel1 (right channel?)... */
2059 status = pjmedia_splitcomb_create_rev_channel(app_config.pool,
2060 app_config.sc,
2061 1 /* ch1 */,
2062 0 /* options */,
2063 &app_config.sc_ch1);
2064 pj_assert(status == PJ_SUCCESS);
2065
2066 /* .. and register it to conference bridge (it would be slot1
2067 * if there's no other devices connected to the bridge)
2068 */
2069 status = pjsua_conf_add_port(app_config.pool, app_config.sc_ch1,
2070 &app_config.sc_ch1_slot);
2071 pj_assert(status == PJ_SUCCESS);
2072
2073 /* Create sound device */
2074 status = pjmedia_snd_port_create(app_config.pool, -1, -1,
2075 conf->info.clock_rate,
2076 2 /* stereo */,
2077 2 * conf->info.samples_per_frame,
2078 conf->info.bits_per_sample,
2079 0, &app_config.snd);
2080 pj_assert(status == PJ_SUCCESS);
2081
2082
2083 /* Connect the splitter to the sound device */
2084 status = pjmedia_snd_port_connect(app_config.snd, app_config.sc);
2085 pj_assert(status == PJ_SUCCESS);
2086}
2087#endif