blob: eb04c7175c52430059a1eefcb0bdb260d190fa6f [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $Id$ */
2/*
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;
1282 pj_status_t status;
1283
1284 /** Create pjsua **/
1285 status = pjsua_create();
1286 if (status != PJ_SUCCESS)
1287 return status;
1288
1289 /* Create pool for application */
1290 app_config.pool = pjsua_pool_create("pjsua-app", 1000, 1000);
1291
1292 /* Init CLI & its FE settings */
1293 if (!app_running) {
1294 pj_cli_cfg_default(&app_config.cli_cfg.cfg);
1295 pj_cli_telnet_cfg_default(&app_config.cli_cfg.telnet_cfg);
1296 pj_cli_console_cfg_default(&app_config.cli_cfg.console_cfg);
1297 app_config.cli_cfg.telnet_cfg.on_started = cli_on_started;
1298 }
1299
1300 /** Parse args **/
1301 status = load_config(app_cfg.argc, app_cfg.argv, &uri_arg);
1302 if (status != PJ_SUCCESS)
1303 return status;
1304
1305 /* Initialize application callbacks */
1306 app_config.cfg.cb.on_call_state = &on_call_state;
1307 app_config.cfg.cb.on_call_media_state = &on_call_media_state;
1308 app_config.cfg.cb.on_incoming_call = &on_incoming_call;
1309 app_config.cfg.cb.on_call_tsx_state = &on_call_tsx_state;
1310 app_config.cfg.cb.on_dtmf_digit = &call_on_dtmf_callback;
1311 app_config.cfg.cb.on_call_redirected = &call_on_redirected;
1312 app_config.cfg.cb.on_reg_state = &on_reg_state;
1313 app_config.cfg.cb.on_incoming_subscribe = &on_incoming_subscribe;
1314 app_config.cfg.cb.on_buddy_state = &on_buddy_state;
1315 app_config.cfg.cb.on_buddy_evsub_state = &on_buddy_evsub_state;
1316 app_config.cfg.cb.on_pager = &on_pager;
1317 app_config.cfg.cb.on_typing = &on_typing;
1318 app_config.cfg.cb.on_call_transfer_status = &on_call_transfer_status;
1319 app_config.cfg.cb.on_call_replaced = &on_call_replaced;
1320 app_config.cfg.cb.on_nat_detect = &on_nat_detect;
1321 app_config.cfg.cb.on_mwi_info = &on_mwi_info;
1322 app_config.cfg.cb.on_transport_state = &on_transport_state;
1323 app_config.cfg.cb.on_ice_transport_error = &on_ice_transport_error;
1324 app_config.cfg.cb.on_snd_dev_operation = &on_snd_dev_operation;
1325 app_config.cfg.cb.on_call_media_event = &on_call_media_event;
1326#ifdef TRANSPORT_ADAPTER_SAMPLE
1327 app_config.cfg.cb.on_create_media_transport = &on_create_media_transport;
1328#endif
1329
1330 /* Set sound device latency */
1331 if (app_config.capture_lat > 0)
1332 app_config.media_cfg.snd_rec_latency = app_config.capture_lat;
1333 if (app_config.playback_lat)
1334 app_config.media_cfg.snd_play_latency = app_config.playback_lat;
1335
1336 if (app_cfg.on_config_init)
1337 (*app_cfg.on_config_init)(&app_config);
1338
1339 /* Initialize pjsua */
1340 status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
1341 &app_config.media_cfg);
1342 if (status != PJ_SUCCESS)
1343 return status;
1344
1345 /* Initialize our module to handle otherwise unhandled request */
1346 status = pjsip_endpt_register_module(pjsua_get_pjsip_endpt(),
1347 &mod_default_handler);
1348 if (status != PJ_SUCCESS)
1349 return status;
1350
1351#ifdef STEREO_DEMO
1352 stereo_demo();
1353#endif
1354
1355 /* Initialize calls data */
1356 for (i=0; i<PJ_ARRAY_SIZE(app_config.call_data); ++i) {
1357 app_config.call_data[i].timer.id = PJSUA_INVALID_ID;
1358 app_config.call_data[i].timer.cb = &call_timeout_callback;
1359 }
1360
1361 /* Optionally registers WAV file */
1362 for (i=0; i<app_config.wav_count; ++i) {
1363 pjsua_player_id wav_id;
1364 unsigned play_options = 0;
1365
1366 if (app_config.auto_play_hangup)
1367 play_options |= PJMEDIA_FILE_NO_LOOP;
1368
1369 status = pjsua_player_create(&app_config.wav_files[i], play_options,
1370 &wav_id);
1371 if (status != PJ_SUCCESS)
1372 goto on_error;
1373
1374 if (app_config.wav_id == PJSUA_INVALID_ID) {
1375 app_config.wav_id = wav_id;
1376 app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id);
1377 if (app_config.auto_play_hangup) {
1378 pjmedia_port *port;
1379
1380 pjsua_player_get_port(app_config.wav_id, &port);
1381 status = pjmedia_wav_player_set_eof_cb(port, NULL,
1382 &on_playfile_done);
1383 if (status != PJ_SUCCESS)
1384 goto on_error;
1385
1386 pj_timer_entry_init(&app_config.auto_hangup_timer, 0, NULL,
1387 &hangup_timeout_callback);
1388 }
1389 }
1390 }
1391
1392 /* Optionally registers tone players */
1393 for (i=0; i<app_config.tone_count; ++i) {
1394 pjmedia_port *tport;
1395 char name[80];
1396 pj_str_t label;
1397 pj_status_t status;
1398
1399 pj_ansi_snprintf(name, sizeof(name), "tone-%d,%d",
1400 app_config.tones[i].freq1,
1401 app_config.tones[i].freq2);
1402 label = pj_str(name);
1403 status = pjmedia_tonegen_create2(app_config.pool, &label,
1404 8000, 1, 160, 16,
1405 PJMEDIA_TONEGEN_LOOP, &tport);
1406 if (status != PJ_SUCCESS) {
1407 pjsua_perror(THIS_FILE, "Unable to create tone generator", status);
1408 goto on_error;
1409 }
1410
1411 status = pjsua_conf_add_port(app_config.pool, tport,
1412 &app_config.tone_slots[i]);
1413 pj_assert(status == PJ_SUCCESS);
1414
1415 status = pjmedia_tonegen_play(tport, 1, &app_config.tones[i], 0);
1416 pj_assert(status == PJ_SUCCESS);
1417 }
1418
1419 /* Optionally create recorder file, if any. */
1420 if (app_config.rec_file.slen) {
1421 status = pjsua_recorder_create(&app_config.rec_file, 0, NULL, 0, 0,
1422 &app_config.rec_id);
1423 if (status != PJ_SUCCESS)
1424 goto on_error;
1425
1426 app_config.rec_port = pjsua_recorder_get_conf_port(app_config.rec_id);
1427 }
1428
1429 pj_memcpy(&tcp_cfg, &app_config.udp_cfg, sizeof(tcp_cfg));
1430
1431 /* Create ringback tones */
1432 if (app_config.no_tones == PJ_FALSE) {
1433 unsigned i, samples_per_frame;
1434 pjmedia_tone_desc tone[RING_CNT+RINGBACK_CNT];
1435 pj_str_t name;
1436
1437 samples_per_frame = app_config.media_cfg.audio_frame_ptime *
1438 app_config.media_cfg.clock_rate *
1439 app_config.media_cfg.channel_count / 1000;
1440
1441 /* Ringback tone (call is ringing) */
1442 name = pj_str("ringback");
1443 status = pjmedia_tonegen_create2(app_config.pool, &name,
1444 app_config.media_cfg.clock_rate,
1445 app_config.media_cfg.channel_count,
1446 samples_per_frame,
1447 16, PJMEDIA_TONEGEN_LOOP,
1448 &app_config.ringback_port);
1449 if (status != PJ_SUCCESS)
1450 goto on_error;
1451
1452 pj_bzero(&tone, sizeof(tone));
1453 for (i=0; i<RINGBACK_CNT; ++i) {
1454 tone[i].freq1 = RINGBACK_FREQ1;
1455 tone[i].freq2 = RINGBACK_FREQ2;
1456 tone[i].on_msec = RINGBACK_ON;
1457 tone[i].off_msec = RINGBACK_OFF;
1458 }
1459 tone[RINGBACK_CNT-1].off_msec = RINGBACK_INTERVAL;
1460
1461 pjmedia_tonegen_play(app_config.ringback_port, RINGBACK_CNT, tone,
1462 PJMEDIA_TONEGEN_LOOP);
1463
1464
1465 status = pjsua_conf_add_port(app_config.pool, app_config.ringback_port,
1466 &app_config.ringback_slot);
1467 if (status != PJ_SUCCESS)
1468 goto on_error;
1469
1470 /* Ring (to alert incoming call) */
1471 name = pj_str("ring");
1472 status = pjmedia_tonegen_create2(app_config.pool, &name,
1473 app_config.media_cfg.clock_rate,
1474 app_config.media_cfg.channel_count,
1475 samples_per_frame,
1476 16, PJMEDIA_TONEGEN_LOOP,
1477 &app_config.ring_port);
1478 if (status != PJ_SUCCESS)
1479 goto on_error;
1480
1481 for (i=0; i<RING_CNT; ++i) {
1482 tone[i].freq1 = RING_FREQ1;
1483 tone[i].freq2 = RING_FREQ2;
1484 tone[i].on_msec = RING_ON;
1485 tone[i].off_msec = RING_OFF;
1486 }
1487 tone[RING_CNT-1].off_msec = RING_INTERVAL;
1488
1489 pjmedia_tonegen_play(app_config.ring_port, RING_CNT,
1490 tone, PJMEDIA_TONEGEN_LOOP);
1491
1492 status = pjsua_conf_add_port(app_config.pool, app_config.ring_port,
1493 &app_config.ring_slot);
1494 if (status != PJ_SUCCESS)
1495 goto on_error;
1496
1497 }
1498
1499 /* Create AVI player virtual devices */
1500 if (app_config.avi_cnt) {
1501#if PJMEDIA_HAS_VIDEO && PJMEDIA_VIDEO_DEV_HAS_AVI
1502 pjmedia_vid_dev_factory *avi_factory;
1503
1504 status = pjmedia_avi_dev_create_factory(pjsua_get_pool_factory(),
1505 app_config.avi_cnt,
1506 &avi_factory);
1507 if (status != PJ_SUCCESS) {
1508 PJ_PERROR(1,(THIS_FILE, status, "Error creating AVI factory"));
1509 goto on_error;
1510 }
1511
1512 for (i=0; i<app_config.avi_cnt; ++i) {
1513 pjmedia_avi_dev_param avdp;
1514 pjmedia_vid_dev_index avid;
1515 unsigned strm_idx, strm_cnt;
1516
1517 app_config.avi[i].dev_id = PJMEDIA_VID_INVALID_DEV;
1518 app_config.avi[i].slot = PJSUA_INVALID_ID;
1519
1520 pjmedia_avi_dev_param_default(&avdp);
1521 avdp.path = app_config.avi[i].path;
1522
1523 status = pjmedia_avi_dev_alloc(avi_factory, &avdp, &avid);
1524 if (status != PJ_SUCCESS) {
1525 PJ_PERROR(1,(THIS_FILE, status,
1526 "Error creating AVI player for %.*s",
1527 (int)avdp.path.slen, avdp.path.ptr));
1528 goto on_error;
1529 }
1530
1531 PJ_LOG(4,(THIS_FILE, "AVI player %.*s created, dev_id=%d",
1532 (int)avdp.title.slen, avdp.title.ptr, avid));
1533
1534 app_config.avi[i].dev_id = avid;
1535 if (app_config.avi_def_idx == PJSUA_INVALID_ID)
1536 app_config.avi_def_idx = i;
1537
1538 strm_cnt = pjmedia_avi_streams_get_num_streams(avdp.avi_streams);
1539 for (strm_idx=0; strm_idx<strm_cnt; ++strm_idx) {
1540 pjmedia_port *aud;
1541 pjmedia_format *fmt;
1542 pjsua_conf_port_id slot;
1543 char fmt_name[5];
1544
1545 aud = pjmedia_avi_streams_get_stream(avdp.avi_streams,
1546 strm_idx);
1547 fmt = &aud->info.fmt;
1548
1549 pjmedia_fourcc_name(fmt->id, fmt_name);
1550
1551 if (fmt->id == PJMEDIA_FORMAT_PCM) {
1552 status = pjsua_conf_add_port(app_config.pool, aud,
1553 &slot);
1554 if (status == PJ_SUCCESS) {
1555 PJ_LOG(4,(THIS_FILE,
1556 "AVI %.*s: audio added to slot %d",
1557 (int)avdp.title.slen, avdp.title.ptr,
1558 slot));
1559 app_config.avi[i].slot = slot;
1560 }
1561 } else {
1562 PJ_LOG(4,(THIS_FILE,
1563 "AVI %.*s: audio ignored, format=%s",
1564 (int)avdp.title.slen, avdp.title.ptr,
1565 fmt_name));
1566 }
1567 }
1568 }
1569#else
1570 PJ_LOG(2,(THIS_FILE,
1571 "Warning: --play-avi is ignored because AVI is disabled"));
1572#endif /* PJMEDIA_VIDEO_DEV_HAS_AVI */
1573 }
1574
1575 /* Add UDP transport unless it's disabled. */
1576 if (!app_config.no_udp) {
1577 pjsua_acc_id aid;
1578 pjsip_transport_type_e type = PJSIP_TRANSPORT_UDP;
1579
1580 status = pjsua_transport_create(type,
1581 &app_config.udp_cfg,
1582 &transport_id);
1583 if (status != PJ_SUCCESS)
1584 goto on_error;
1585
1586 /* Add local account */
1587 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1588 if (PJMEDIA_HAS_VIDEO) {
1589 pjsua_acc_config acc_cfg;
1590 pjsua_acc_get_config(aid, &acc_cfg);
1591 app_config_init_video(&acc_cfg);
1592 pjsua_acc_modify(aid, &acc_cfg);
1593 }
1594 //pjsua_acc_set_transport(aid, transport_id);
1595 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1596
1597 if (app_config.udp_cfg.port == 0) {
1598 pjsua_transport_info ti;
1599 pj_sockaddr_in *a;
1600
1601 pjsua_transport_get_info(transport_id, &ti);
1602 a = (pj_sockaddr_in*)&ti.local_addr;
1603
1604 tcp_cfg.port = pj_ntohs(a->sin_port);
1605 }
1606 }
1607
1608 /* Add UDP IPv6 transport unless it's disabled. */
1609 if (!app_config.no_udp && app_config.ipv6) {
1610 pjsua_acc_id aid;
1611 pjsip_transport_type_e type = PJSIP_TRANSPORT_UDP6;
1612 pjsua_transport_config udp_cfg;
1613
1614 udp_cfg = app_config.udp_cfg;
1615 if (udp_cfg.port == 0)
1616 udp_cfg.port = 5060;
1617 else
1618 udp_cfg.port += 10;
1619 status = pjsua_transport_create(type,
1620 &udp_cfg,
1621 &transport_id);
1622 if (status != PJ_SUCCESS)
1623 goto on_error;
1624
1625 /* Add local account */
1626 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1627 if (PJMEDIA_HAS_VIDEO) {
1628 pjsua_acc_config acc_cfg;
1629 pjsua_acc_get_config(aid, &acc_cfg);
1630 app_config_init_video(&acc_cfg);
1631 if (app_config.ipv6)
1632 acc_cfg.ipv6_media_use = PJSUA_IPV6_ENABLED;
1633 pjsua_acc_modify(aid, &acc_cfg);
1634 }
1635 //pjsua_acc_set_transport(aid, transport_id);
1636 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1637
1638 if (app_config.udp_cfg.port == 0) {
1639 pjsua_transport_info ti;
1640 pj_sockaddr_in *a;
1641
1642 pjsua_transport_get_info(transport_id, &ti);
1643 a = (pj_sockaddr_in*)&ti.local_addr;
1644
1645 tcp_cfg.port = pj_ntohs(a->sin_port);
1646 }
1647 }
1648
1649 /* Add TCP transport unless it's disabled */
1650 if (!app_config.no_tcp) {
1651 pjsua_acc_id aid;
1652
1653 status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
1654 &tcp_cfg,
1655 &transport_id);
1656 if (status != PJ_SUCCESS)
1657 goto on_error;
1658
1659 /* Add local account */
1660 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1661 if (PJMEDIA_HAS_VIDEO) {
1662 pjsua_acc_config acc_cfg;
1663 pjsua_acc_get_config(aid, &acc_cfg);
1664 app_config_init_video(&acc_cfg);
1665 pjsua_acc_modify(aid, &acc_cfg);
1666 }
1667 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1668
1669 }
1670
1671 /* Add TCP IPv6 transport unless it's disabled. */
1672 if (!app_config.no_tcp && app_config.ipv6) {
1673 pjsua_acc_id aid;
1674 pjsip_transport_type_e type = PJSIP_TRANSPORT_TCP6;
1675
1676 tcp_cfg.port += 10;
1677
1678 status = pjsua_transport_create(type,
1679 &tcp_cfg,
1680 &transport_id);
1681 if (status != PJ_SUCCESS)
1682 goto on_error;
1683
1684 /* Add local account */
1685 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1686 if (PJMEDIA_HAS_VIDEO) {
1687 pjsua_acc_config acc_cfg;
1688 pjsua_acc_get_config(aid, &acc_cfg);
1689 app_config_init_video(&acc_cfg);
1690 if (app_config.ipv6)
1691 acc_cfg.ipv6_media_use = PJSUA_IPV6_ENABLED;
1692 pjsua_acc_modify(aid, &acc_cfg);
1693 }
1694 //pjsua_acc_set_transport(aid, transport_id);
1695 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1696 }
1697
1698
1699#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
1700 /* Add TLS transport when application wants one */
1701 if (app_config.use_tls) {
1702
1703 pjsua_acc_id acc_id;
1704
1705 /* Copy the QoS settings */
1706 tcp_cfg.tls_setting.qos_type = tcp_cfg.qos_type;
1707 pj_memcpy(&tcp_cfg.tls_setting.qos_params, &tcp_cfg.qos_params,
1708 sizeof(tcp_cfg.qos_params));
1709
1710 /* Set TLS port as TCP port+1 */
1711 tcp_cfg.port++;
1712 status = pjsua_transport_create(PJSIP_TRANSPORT_TLS,
1713 &tcp_cfg,
1714 &transport_id);
1715 tcp_cfg.port--;
1716 if (status != PJ_SUCCESS)
1717 goto on_error;
1718
1719 /* Add local account */
1720 pjsua_acc_add_local(transport_id, PJ_FALSE, &acc_id);
1721 if (PJMEDIA_HAS_VIDEO) {
1722 pjsua_acc_config acc_cfg;
1723 pjsua_acc_get_config(acc_id, &acc_cfg);
1724 app_config_init_video(&acc_cfg);
1725 pjsua_acc_modify(acc_id, &acc_cfg);
1726 }
1727 pjsua_acc_set_online_status(acc_id, PJ_TRUE);
1728 }
1729
1730 /* Add TLS IPv6 transport unless it's disabled. */
1731 if (app_config.use_tls && app_config.ipv6) {
1732 pjsua_acc_id aid;
1733 pjsip_transport_type_e type = PJSIP_TRANSPORT_TLS6;
1734
1735 tcp_cfg.port += 10;
1736
1737 status = pjsua_transport_create(type,
1738 &tcp_cfg,
1739 &transport_id);
1740 if (status != PJ_SUCCESS)
1741 goto on_error;
1742
1743 /* Add local account */
1744 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1745 if (PJMEDIA_HAS_VIDEO) {
1746 pjsua_acc_config acc_cfg;
1747 pjsua_acc_get_config(aid, &acc_cfg);
1748 app_config_init_video(&acc_cfg);
1749 if (app_config.ipv6)
1750 acc_cfg.ipv6_media_use = PJSUA_IPV6_ENABLED;
1751 pjsua_acc_modify(aid, &acc_cfg);
1752 }
1753 //pjsua_acc_set_transport(aid, transport_id);
1754 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1755 }
1756
1757#endif
1758
1759 if (transport_id == -1) {
1760 PJ_LOG(1,(THIS_FILE, "Error: no transport is configured"));
1761 status = -1;
1762 goto on_error;
1763 }
1764
1765
1766 /* Add accounts */
1767 for (i=0; i<app_config.acc_cnt; ++i) {
1768 app_config.acc_cfg[i].rtp_cfg = app_config.rtp_cfg;
1769 app_config.acc_cfg[i].reg_retry_interval = 300;
1770 app_config.acc_cfg[i].reg_first_retry_interval = 60;
1771
1772 app_config_init_video(&app_config.acc_cfg[i]);
1773
1774 status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL);
1775 if (status != PJ_SUCCESS)
1776 goto on_error;
1777 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1778 }
1779
1780 /* Add buddies */
1781 for (i=0; i<app_config.buddy_cnt; ++i) {
1782 status = pjsua_buddy_add(&app_config.buddy_cfg[i], NULL);
1783 if (status != PJ_SUCCESS) {
1784 PJ_PERROR(1,(THIS_FILE, status, "Error adding buddy"));
1785 goto on_error;
1786 }
1787 }
1788
1789 /* Optionally disable some codec */
1790 for (i=0; i<app_config.codec_dis_cnt; ++i) {
1791 pjsua_codec_set_priority(&app_config.codec_dis[i],
1792 PJMEDIA_CODEC_PRIO_DISABLED);
1793#if PJSUA_HAS_VIDEO
1794 pjsua_vid_codec_set_priority(&app_config.codec_dis[i],
1795 PJMEDIA_CODEC_PRIO_DISABLED);
1796#endif
1797 }
1798
1799 /* Optionally set codec orders */
1800 for (i=0; i<app_config.codec_cnt; ++i) {
1801 pjsua_codec_set_priority(&app_config.codec_arg[i],
1802 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
1803#if PJSUA_HAS_VIDEO
1804 pjsua_vid_codec_set_priority(&app_config.codec_arg[i],
1805 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
1806#endif
1807 }
1808
1809 /* Use null sound device? */
1810#ifndef STEREO_DEMO
1811 if (app_config.null_audio) {
1812 status = pjsua_set_null_snd_dev();
1813 if (status != PJ_SUCCESS)
1814 return status;
1815 }
1816#endif
1817
1818 if (app_config.capture_dev != PJSUA_INVALID_ID ||
1819 app_config.playback_dev != PJSUA_INVALID_ID)
1820 {
1821 status = pjsua_set_snd_dev(app_config.capture_dev,
1822 app_config.playback_dev);
1823 if (status != PJ_SUCCESS)
1824 goto on_error;
1825 }
1826
1827 /* Init call setting */
1828 pjsua_call_setting_default(&call_opt);
1829 call_opt.aud_cnt = app_config.aud_cnt;
1830 call_opt.vid_cnt = app_config.vid.vid_cnt;
1831
1832 return PJ_SUCCESS;
1833
1834on_error:
1835 app_destroy();
1836 return status;
1837}
1838
1839pj_status_t pjsua_app_init(const pjsua_app_cfg_t *cfg)
1840{
1841 pj_status_t status;
1842 pj_memcpy(&app_cfg, cfg, sizeof(app_cfg));
1843
1844 status = app_init();
1845 if (status != PJ_SUCCESS)
1846 return status;
1847
1848 /* Init CLI if configured */
1849 if (app_config.use_cli) {
1850 status = cli_init();
1851 }
1852 return status;
1853}
1854
1855pj_status_t pjsua_app_run(pj_bool_t wait_telnet_cli)
1856{
1857 pj_thread_t *stdout_refresh_thread = NULL;
1858 pj_status_t status;
1859
1860 /* Start console refresh thread */
1861 if (stdout_refresh > 0) {
1862 pj_thread_create(app_config.pool, "stdout", &stdout_refresh_proc,
1863 NULL, 0, 0, &stdout_refresh_thread);
1864 }
1865
1866 status = pjsua_start();
1867 if (status != PJ_SUCCESS)
1868 goto on_return;
1869
1870 if (app_config.use_cli && (app_config.cli_cfg.cli_fe & CLI_FE_TELNET)) {
1871 char info[128];
1872 cli_get_info(info, sizeof(info));
1873 if (app_cfg.on_started) {
1874 (*app_cfg.on_started)(status, info);
1875 }
1876 } else {
1877 if (app_cfg.on_started) {
1878 (*app_cfg.on_started)(status, "Ready");
1879 }
1880 }
1881
1882 /* If user specifies URI to call, then call the URI */
1883 if (uri_arg.slen) {
1884 pjsua_call_setting_default(&call_opt);
1885 call_opt.aud_cnt = app_config.aud_cnt;
1886 call_opt.vid_cnt = app_config.vid.vid_cnt;
1887
1888 pjsua_call_make_call(current_acc, &uri_arg, &call_opt, NULL,
1889 NULL, NULL);
1890 }
1891
1892 app_running = PJ_TRUE;
1893
1894 if (app_config.use_cli)
1895 cli_main(wait_telnet_cli);
1896 else
1897 legacy_main();
1898
1899 status = PJ_SUCCESS;
1900
1901on_return:
1902 if (stdout_refresh_thread) {
1903 stdout_refresh_quit = PJ_TRUE;
1904 pj_thread_join(stdout_refresh_thread);
1905 pj_thread_destroy(stdout_refresh_thread);
1906 stdout_refresh_quit = PJ_FALSE;
1907 }
1908 return status;
1909}
1910
1911static pj_status_t app_destroy()
1912{
1913 pj_status_t status = PJ_SUCCESS;
1914 unsigned i;
1915 pj_bool_t use_cli = PJ_FALSE;
1916 int cli_fe = 0;
1917 pj_uint16_t cli_telnet_port = 0;
1918
1919#ifdef STEREO_DEMO
1920 if (app_config.snd) {
1921 pjmedia_snd_port_destroy(app_config.snd);
1922 app_config.snd = NULL;
1923 }
1924 if (app_config.sc_ch1) {
1925 pjsua_conf_remove_port(app_config.sc_ch1_slot);
1926 app_config.sc_ch1_slot = PJSUA_INVALID_ID;
1927 pjmedia_port_destroy(app_config.sc_ch1);
1928 app_config.sc_ch1 = NULL;
1929 }
1930 if (app_config.sc) {
1931 pjmedia_port_destroy(app_config.sc);
1932 app_config.sc = NULL;
1933 }
1934#endif
1935
1936 /* Close avi devs and ports */
1937 for (i=0; i<app_config.avi_cnt; ++i) {
1938 if (app_config.avi[i].slot != PJSUA_INVALID_ID)
1939 pjsua_conf_remove_port(app_config.avi[i].slot);
1940#if PJMEDIA_HAS_VIDEO && PJMEDIA_VIDEO_DEV_HAS_AVI
1941 if (app_config.avi[i].dev_id != PJMEDIA_VID_INVALID_DEV)
1942 pjmedia_avi_dev_free(app_config.avi[i].dev_id);
1943#endif
1944 }
1945
1946 /* Close ringback port */
1947 if (app_config.ringback_port &&
1948 app_config.ringback_slot != PJSUA_INVALID_ID)
1949 {
1950 pjsua_conf_remove_port(app_config.ringback_slot);
1951 app_config.ringback_slot = PJSUA_INVALID_ID;
1952 pjmedia_port_destroy(app_config.ringback_port);
1953 app_config.ringback_port = NULL;
1954 }
1955
1956 /* Close ring port */
1957 if (app_config.ring_port && app_config.ring_slot != PJSUA_INVALID_ID) {
1958 pjsua_conf_remove_port(app_config.ring_slot);
1959 app_config.ring_slot = PJSUA_INVALID_ID;
1960 pjmedia_port_destroy(app_config.ring_port);
1961 app_config.ring_port = NULL;
1962 }
1963
1964 /* Close tone generators */
1965 for (i=0; i<app_config.tone_count; ++i) {
1966 pjsua_conf_remove_port(app_config.tone_slots[i]);
1967 }
1968
1969 if (app_config.pool) {
1970 pj_pool_release(app_config.pool);
1971 app_config.pool = NULL;
1972 }
1973
1974 status = pjsua_destroy();
1975
1976 if (app_config.use_cli) {
1977 use_cli = app_config.use_cli;
1978 cli_fe = app_config.cli_cfg.cli_fe;
1979 cli_telnet_port = app_config.cli_cfg.telnet_cfg.port;
1980 }
1981
1982 /* Reset config */
1983 pj_bzero(&app_config, sizeof(app_config));
1984
1985 if (use_cli) {
1986 app_config.use_cli = use_cli;
1987 app_config.cli_cfg.cli_fe = cli_fe;
1988 app_config.cli_cfg.telnet_cfg.port = cli_telnet_port;
1989 }
1990
1991 return status;
1992}
1993
1994pj_status_t pjsua_app_destroy()
1995{
1996 pj_status_t status;
1997
1998 status = app_destroy();
1999
2000 if (app_config.use_cli) {
2001 cli_destroy();
2002 }
2003
2004 return status;
2005}
2006
2007/** ======================= **/
2008
2009#ifdef STEREO_DEMO
2010/*
2011 * In this stereo demo, we open the sound device in stereo mode and
2012 * arrange the attachment to the PJSUA-LIB conference bridge as such
2013 * so that channel0/left channel of the sound device corresponds to
2014 * slot 0 in the bridge, and channel1/right channel of the sound
2015 * device corresponds to slot 1 in the bridge. Then user can independently
2016 * feed different media to/from the speakers/microphones channels, by
2017 * connecting them to slot 0 or 1 respectively.
2018 *
2019 * Here's how the connection looks like:
2020 *
2021 +-----------+ stereo +-----------------+ 2x mono +-----------+
2022 | AUDIO DEV |<------>| SPLITCOMB left|<------->|#0 BRIDGE |
2023 +-----------+ | right|<------->|#1 |
2024 +-----------------+ +-----------+
2025 */
2026static void stereo_demo()
2027{
2028 pjmedia_port *conf;
2029 pj_status_t status;
2030
2031 /* Disable existing sound device */
2032 conf = pjsua_set_no_snd_dev();
2033
2034 /* Create stereo-mono splitter/combiner */
2035 status = pjmedia_splitcomb_create(app_config.pool,
2036 conf->info.clock_rate /* clock rate */,
2037 2 /* stereo */,
2038 2 * conf->info.samples_per_frame,
2039 conf->info.bits_per_sample,
2040 0 /* options */,
2041 &app_config.sc);
2042 pj_assert(status == PJ_SUCCESS);
2043
2044 /* Connect channel0 (left channel?) to conference port slot0 */
2045 status = pjmedia_splitcomb_set_channel(app_config.sc, 0 /* ch0 */,
2046 0 /*options*/,
2047 conf);
2048 pj_assert(status == PJ_SUCCESS);
2049
2050 /* Create reverse channel for channel1 (right channel?)... */
2051 status = pjmedia_splitcomb_create_rev_channel(app_config.pool,
2052 app_config.sc,
2053 1 /* ch1 */,
2054 0 /* options */,
2055 &app_config.sc_ch1);
2056 pj_assert(status == PJ_SUCCESS);
2057
2058 /* .. and register it to conference bridge (it would be slot1
2059 * if there's no other devices connected to the bridge)
2060 */
2061 status = pjsua_conf_add_port(app_config.pool, app_config.sc_ch1,
2062 &app_config.sc_ch1_slot);
2063 pj_assert(status == PJ_SUCCESS);
2064
2065 /* Create sound device */
2066 status = pjmedia_snd_port_create(app_config.pool, -1, -1,
2067 conf->info.clock_rate,
2068 2 /* stereo */,
2069 2 * conf->info.samples_per_frame,
2070 conf->info.bits_per_sample,
2071 0, &app_config.snd);
2072 pj_assert(status == PJ_SUCCESS);
2073
2074
2075 /* Connect the splitter to the sound device */
2076 status = pjmedia_snd_port_connect(app_config.snd, app_config.sc);
2077 pj_assert(status == PJ_SUCCESS);
2078}
2079#endif