blob: 57aba6f00bc67c9e5b53a767390fd1b620e4e045 [file] [log] [blame]
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001/* $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?"ca a":"a"),
329 (app_config.use_cli?"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 = (char *)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_expires_hdr *)pjsip_msg_find_hdr(rdata->msg_info.msg,
1085 PJSIP_H_EXPIRES, NULL);
1086
1087 h = rdata->msg_info.msg->hdr.next;
1088 while (h != &rdata->msg_info.msg->hdr) {
1089 if (h->type == PJSIP_H_CONTACT) {
1090 const pjsip_contact_hdr *c = (const pjsip_contact_hdr*)h;
1091 int e = c->expires;
1092
1093 if (e < 0) {
1094 if (exp)
1095 e = exp->ivalue;
1096 else
1097 e = 3600;
1098 }
1099
1100 if (e > 0) {
1101 pjsip_contact_hdr *nc = (pjsip_contact_hdr *)pjsip_hdr_clone(
1102 tdata->pool, h);
1103 nc->expires = e;
1104 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)nc);
1105 ++cnt;
1106 }
1107 }
1108 h = h->next;
1109 }
1110
1111 srv = pjsip_generic_string_hdr_create(tdata->pool, NULL, NULL);
1112 srv->name = pj_str("Server");
1113 srv->hvalue = pj_str("pjsua simple registrar");
1114 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)srv);
1115
1116 pjsip_endpt_send_response2(pjsua_get_pjsip_endpt(),
1117 rdata, tdata, NULL, NULL);
1118}
1119
1120/*****************************************************************************
1121 * A simple module to handle otherwise unhandled request. We will register
1122 * this with the lowest priority.
1123 */
1124
1125/* Notification on incoming request */
1126static pj_bool_t default_mod_on_rx_request(pjsip_rx_data *rdata)
1127{
1128 pjsip_tx_data *tdata;
1129 pjsip_status_code status_code;
1130 pj_status_t status;
1131
1132 /* Don't respond to ACK! */
1133 if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
1134 &pjsip_ack_method) == 0)
1135 return PJ_TRUE;
1136
1137 /* Simple registrar */
1138 if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
1139 &pjsip_register_method) == 0)
1140 {
1141 simple_registrar(rdata);
1142 return PJ_TRUE;
1143 }
1144
1145 /* Create basic response. */
1146 if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
1147 &pjsip_notify_method) == 0)
1148 {
1149 /* Unsolicited NOTIFY's, send with Bad Request */
1150 status_code = PJSIP_SC_BAD_REQUEST;
1151 } else {
1152 /* Probably unknown method */
1153 status_code = PJSIP_SC_METHOD_NOT_ALLOWED;
1154 }
1155 status = pjsip_endpt_create_response(pjsua_get_pjsip_endpt(),
1156 rdata, status_code,
1157 NULL, &tdata);
1158 if (status != PJ_SUCCESS) {
1159 pjsua_perror(THIS_FILE, "Unable to create response", status);
1160 return PJ_TRUE;
1161 }
1162
1163 /* Add Allow if we're responding with 405 */
1164 if (status_code == PJSIP_SC_METHOD_NOT_ALLOWED) {
1165 const pjsip_hdr *cap_hdr;
1166 cap_hdr = pjsip_endpt_get_capability(pjsua_get_pjsip_endpt(),
1167 PJSIP_H_ALLOW, NULL);
1168 if (cap_hdr) {
1169 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)pjsip_hdr_clone(
1170 tdata->pool, cap_hdr));
1171 }
1172 }
1173
1174 /* Add User-Agent header */
1175 {
1176 pj_str_t user_agent;
1177 char tmp[80];
1178 const pj_str_t USER_AGENT = { "User-Agent", 10};
1179 pjsip_hdr *h;
1180
1181 pj_ansi_snprintf(tmp, sizeof(tmp), "PJSUA v%s/%s",
1182 pj_get_version(), PJ_OS_NAME);
1183 pj_strdup2_with_null(tdata->pool, &user_agent, tmp);
1184
1185 h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool,
1186 &USER_AGENT,
1187 &user_agent);
1188 pjsip_msg_add_hdr(tdata->msg, h);
1189 }
1190
1191 pjsip_endpt_send_response2(pjsua_get_pjsip_endpt(), rdata, tdata,
1192 NULL, NULL);
1193
1194 return PJ_TRUE;
1195}
1196
1197/* The module instance. */
1198static pjsip_module mod_default_handler =
1199{
1200 NULL, NULL, /* prev, next. */
1201 { "mod-default-handler", 19 }, /* Name. */
1202 -1, /* Id */
1203 PJSIP_MOD_PRIORITY_APPLICATION+99, /* Priority */
1204 NULL, /* load() */
1205 NULL, /* start() */
1206 NULL, /* stop() */
1207 NULL, /* unload() */
1208 &default_mod_on_rx_request, /* on_rx_request() */
1209 NULL, /* on_rx_response() */
1210 NULL, /* on_tx_request. */
1211 NULL, /* on_tx_response() */
1212 NULL, /* on_tsx_state() */
1213
1214};
1215
1216/** CLI callback **/
1217
1218/* Called on CLI (re)started, e.g: initial start, after iOS bg */
1219void cli_on_started(pj_status_t status)
1220{
1221 /* Notify app */
1222 if (app_cfg.on_started) {
1223 if (status == PJ_SUCCESS) {
1224 char info[128];
1225 cli_get_info(info, sizeof(info));
1226 if (app_cfg.on_started) {
1227 (*app_cfg.on_started)(status, info);
1228 }
1229 } else {
1230 if (app_cfg.on_started) {
1231 (*app_cfg.on_started)(status, NULL);
1232 }
1233 }
1234 }
1235}
1236
1237/* Called on CLI quit */
1238void cli_on_stopped(pj_bool_t restart, int argc, char* argv[])
1239{
1240 /* Notify app */
1241 if (app_cfg.on_stopped)
1242 (*app_cfg.on_stopped)(restart, argc, argv);
1243}
1244
1245
1246/* Called on pjsua legacy quit */
1247void legacy_on_stopped(pj_bool_t restart)
1248{
1249 /* Notify app */
1250 if (app_cfg.on_stopped)
1251 (*app_cfg.on_stopped)(restart, 1, NULL);
1252}
1253
1254/*****************************************************************************
1255 * Public API
1256 */
1257
1258int stdout_refresh_proc(void *arg)
1259{
1260 extern char *stdout_refresh_text;
1261
1262 PJ_UNUSED_ARG(arg);
1263
1264 /* Set thread to lowest priority so that it doesn't clobber
1265 * stdout output
1266 */
1267 pj_thread_set_prio(pj_thread_this(),
1268 pj_thread_get_prio_min(pj_thread_this()));
1269
1270 while (!stdout_refresh_quit) {
1271 pj_thread_sleep(stdout_refresh * 1000);
1272 puts(stdout_refresh_text);
1273 fflush(stdout);
1274 }
1275
1276 return 0;
1277}
1278
1279static pj_status_t app_init()
1280{
1281 pjsua_transport_id transport_id = -1;
1282 pjsua_transport_config tcp_cfg;
1283 unsigned i;
1284 pj_pool_t *tmp_pool;
1285 pj_status_t status;
1286
1287 /** Create pjsua **/
1288 status = pjsua_create();
1289 if (status != PJ_SUCCESS)
1290 return status;
1291
1292 /* Create pool for application */
1293 app_config.pool = pjsua_pool_create("pjsua-app", 1000, 1000);
1294 tmp_pool = pjsua_pool_create("tmp-pjsua", 1000, 1000);;
1295
1296 /* Init CLI & its FE settings */
1297 if (!app_running) {
1298 pj_cli_cfg_default(&app_config.cli_cfg.cfg);
1299 pj_cli_telnet_cfg_default(&app_config.cli_cfg.telnet_cfg);
1300 pj_cli_console_cfg_default(&app_config.cli_cfg.console_cfg);
1301 app_config.cli_cfg.telnet_cfg.on_started = cli_on_started;
1302 }
1303
1304 /** Parse args **/
1305 status = load_config(app_cfg.argc, app_cfg.argv, &uri_arg);
1306 if (status != PJ_SUCCESS) {
1307 pj_pool_release(tmp_pool);
1308 return status;
1309 }
1310
1311 /* Initialize application callbacks */
1312 app_config.cfg.cb.on_call_state = &on_call_state;
1313 app_config.cfg.cb.on_call_media_state = &on_call_media_state;
1314 app_config.cfg.cb.on_incoming_call = &on_incoming_call;
1315 app_config.cfg.cb.on_call_tsx_state = &on_call_tsx_state;
1316 app_config.cfg.cb.on_dtmf_digit = &call_on_dtmf_callback;
1317 app_config.cfg.cb.on_call_redirected = &call_on_redirected;
1318 app_config.cfg.cb.on_reg_state = &on_reg_state;
1319 app_config.cfg.cb.on_incoming_subscribe = &on_incoming_subscribe;
1320 app_config.cfg.cb.on_buddy_state = &on_buddy_state;
1321 app_config.cfg.cb.on_buddy_evsub_state = &on_buddy_evsub_state;
1322 app_config.cfg.cb.on_pager = &on_pager;
1323 app_config.cfg.cb.on_typing = &on_typing;
1324 app_config.cfg.cb.on_call_transfer_status = &on_call_transfer_status;
1325 app_config.cfg.cb.on_call_replaced = &on_call_replaced;
1326 app_config.cfg.cb.on_nat_detect = &on_nat_detect;
1327 app_config.cfg.cb.on_mwi_info = &on_mwi_info;
1328 app_config.cfg.cb.on_transport_state = &on_transport_state;
1329 app_config.cfg.cb.on_ice_transport_error = &on_ice_transport_error;
1330 app_config.cfg.cb.on_snd_dev_operation = &on_snd_dev_operation;
1331 app_config.cfg.cb.on_call_media_event = &on_call_media_event;
1332#ifdef TRANSPORT_ADAPTER_SAMPLE
1333 app_config.cfg.cb.on_create_media_transport = &on_create_media_transport;
1334#endif
1335
1336 /* Set sound device latency */
1337 if (app_config.capture_lat > 0)
1338 app_config.media_cfg.snd_rec_latency = app_config.capture_lat;
1339 if (app_config.playback_lat)
1340 app_config.media_cfg.snd_play_latency = app_config.playback_lat;
1341
1342 if (app_cfg.on_config_init)
1343 (*app_cfg.on_config_init)(&app_config);
1344
1345 /* Initialize pjsua */
1346 status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
1347 &app_config.media_cfg);
1348 if (status != PJ_SUCCESS) {
1349 pj_pool_release(tmp_pool);
1350 return status;
1351 }
1352
1353 /* Initialize our module to handle otherwise unhandled request */
1354 status = pjsip_endpt_register_module(pjsua_get_pjsip_endpt(),
1355 &mod_default_handler);
1356 if (status != PJ_SUCCESS)
1357 return status;
1358
1359#ifdef STEREO_DEMO
1360 stereo_demo();
1361#endif
1362
1363 /* Initialize calls data */
1364 for (i=0; i<PJ_ARRAY_SIZE(app_config.call_data); ++i) {
1365 app_config.call_data[i].timer.id = PJSUA_INVALID_ID;
1366 app_config.call_data[i].timer.cb = &call_timeout_callback;
1367 }
1368
1369 /* Optionally registers WAV file */
1370 for (i=0; i<app_config.wav_count; ++i) {
1371 pjsua_player_id wav_id;
1372 unsigned play_options = 0;
1373
1374 if (app_config.auto_play_hangup)
1375 play_options |= PJMEDIA_FILE_NO_LOOP;
1376
1377 status = pjsua_player_create(&app_config.wav_files[i], play_options,
1378 &wav_id);
1379 if (status != PJ_SUCCESS)
1380 goto on_error;
1381
1382 if (app_config.wav_id == PJSUA_INVALID_ID) {
1383 app_config.wav_id = wav_id;
1384 app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id);
1385 if (app_config.auto_play_hangup) {
1386 pjmedia_port *port;
1387
1388 pjsua_player_get_port(app_config.wav_id, &port);
1389 status = pjmedia_wav_player_set_eof_cb(port, NULL,
1390 &on_playfile_done);
1391 if (status != PJ_SUCCESS)
1392 goto on_error;
1393
1394 pj_timer_entry_init(&app_config.auto_hangup_timer, 0, NULL,
1395 &hangup_timeout_callback);
1396 }
1397 }
1398 }
1399
1400 /* Optionally registers tone players */
1401 for (i=0; i<app_config.tone_count; ++i) {
1402 pjmedia_port *tport;
1403 char name[80];
1404 pj_str_t label;
1405 pj_status_t status;
1406
1407 pj_ansi_snprintf(name, sizeof(name), "tone-%d,%d",
1408 app_config.tones[i].freq1,
1409 app_config.tones[i].freq2);
1410 label = pj_str(name);
1411 status = pjmedia_tonegen_create2(app_config.pool, &label,
1412 8000, 1, 160, 16,
1413 PJMEDIA_TONEGEN_LOOP, &tport);
1414 if (status != PJ_SUCCESS) {
1415 pjsua_perror(THIS_FILE, "Unable to create tone generator", status);
1416 goto on_error;
1417 }
1418
1419 status = pjsua_conf_add_port(app_config.pool, tport,
1420 &app_config.tone_slots[i]);
1421 pj_assert(status == PJ_SUCCESS);
1422
1423 status = pjmedia_tonegen_play(tport, 1, &app_config.tones[i], 0);
1424 pj_assert(status == PJ_SUCCESS);
1425 }
1426
1427 /* Optionally create recorder file, if any. */
1428 if (app_config.rec_file.slen) {
1429 status = pjsua_recorder_create(&app_config.rec_file, 0, NULL, 0, 0,
1430 &app_config.rec_id);
1431 if (status != PJ_SUCCESS)
1432 goto on_error;
1433
1434 app_config.rec_port = pjsua_recorder_get_conf_port(app_config.rec_id);
1435 }
1436
1437 pj_memcpy(&tcp_cfg, &app_config.udp_cfg, sizeof(tcp_cfg));
1438
1439 /* Create ringback tones */
1440 if (app_config.no_tones == PJ_FALSE) {
1441 unsigned i, samples_per_frame;
1442 pjmedia_tone_desc tone[RING_CNT+RINGBACK_CNT];
1443 pj_str_t name;
1444
1445 samples_per_frame = app_config.media_cfg.audio_frame_ptime *
1446 app_config.media_cfg.clock_rate *
1447 app_config.media_cfg.channel_count / 1000;
1448
1449 /* Ringback tone (call is ringing) */
1450 name = pj_str("ringback");
1451 status = pjmedia_tonegen_create2(app_config.pool, &name,
1452 app_config.media_cfg.clock_rate,
1453 app_config.media_cfg.channel_count,
1454 samples_per_frame,
1455 16, PJMEDIA_TONEGEN_LOOP,
1456 &app_config.ringback_port);
1457 if (status != PJ_SUCCESS)
1458 goto on_error;
1459
1460 pj_bzero(&tone, sizeof(tone));
1461 for (i=0; i<RINGBACK_CNT; ++i) {
1462 tone[i].freq1 = RINGBACK_FREQ1;
1463 tone[i].freq2 = RINGBACK_FREQ2;
1464 tone[i].on_msec = RINGBACK_ON;
1465 tone[i].off_msec = RINGBACK_OFF;
1466 }
1467 tone[RINGBACK_CNT-1].off_msec = RINGBACK_INTERVAL;
1468
1469 pjmedia_tonegen_play(app_config.ringback_port, RINGBACK_CNT, tone,
1470 PJMEDIA_TONEGEN_LOOP);
1471
1472
1473 status = pjsua_conf_add_port(app_config.pool, app_config.ringback_port,
1474 &app_config.ringback_slot);
1475 if (status != PJ_SUCCESS)
1476 goto on_error;
1477
1478 /* Ring (to alert incoming call) */
1479 name = pj_str("ring");
1480 status = pjmedia_tonegen_create2(app_config.pool, &name,
1481 app_config.media_cfg.clock_rate,
1482 app_config.media_cfg.channel_count,
1483 samples_per_frame,
1484 16, PJMEDIA_TONEGEN_LOOP,
1485 &app_config.ring_port);
1486 if (status != PJ_SUCCESS)
1487 goto on_error;
1488
1489 for (i=0; i<RING_CNT; ++i) {
1490 tone[i].freq1 = RING_FREQ1;
1491 tone[i].freq2 = RING_FREQ2;
1492 tone[i].on_msec = RING_ON;
1493 tone[i].off_msec = RING_OFF;
1494 }
1495 tone[RING_CNT-1].off_msec = RING_INTERVAL;
1496
1497 pjmedia_tonegen_play(app_config.ring_port, RING_CNT,
1498 tone, PJMEDIA_TONEGEN_LOOP);
1499
1500 status = pjsua_conf_add_port(app_config.pool, app_config.ring_port,
1501 &app_config.ring_slot);
1502 if (status != PJ_SUCCESS)
1503 goto on_error;
1504
1505 }
1506
1507 /* Create AVI player virtual devices */
1508 if (app_config.avi_cnt) {
1509#if PJMEDIA_HAS_VIDEO && PJMEDIA_VIDEO_DEV_HAS_AVI
1510 pjmedia_vid_dev_factory *avi_factory;
1511
1512 status = pjmedia_avi_dev_create_factory(pjsua_get_pool_factory(),
1513 app_config.avi_cnt,
1514 &avi_factory);
1515 if (status != PJ_SUCCESS) {
1516 PJ_PERROR(1,(THIS_FILE, status, "Error creating AVI factory"));
1517 goto on_error;
1518 }
1519
1520 for (i=0; i<app_config.avi_cnt; ++i) {
1521 pjmedia_avi_dev_param avdp;
1522 pjmedia_vid_dev_index avid;
1523 unsigned strm_idx, strm_cnt;
1524
1525 app_config.avi[i].dev_id = PJMEDIA_VID_INVALID_DEV;
1526 app_config.avi[i].slot = PJSUA_INVALID_ID;
1527
1528 pjmedia_avi_dev_param_default(&avdp);
1529 avdp.path = app_config.avi[i].path;
1530
1531 status = pjmedia_avi_dev_alloc(avi_factory, &avdp, &avid);
1532 if (status != PJ_SUCCESS) {
1533 PJ_PERROR(1,(THIS_FILE, status,
1534 "Error creating AVI player for %.*s",
1535 (int)avdp.path.slen, avdp.path.ptr));
1536 goto on_error;
1537 }
1538
1539 PJ_LOG(4,(THIS_FILE, "AVI player %.*s created, dev_id=%d",
1540 (int)avdp.title.slen, avdp.title.ptr, avid));
1541
1542 app_config.avi[i].dev_id = avid;
1543 if (app_config.avi_def_idx == PJSUA_INVALID_ID)
1544 app_config.avi_def_idx = i;
1545
1546 strm_cnt = pjmedia_avi_streams_get_num_streams(avdp.avi_streams);
1547 for (strm_idx=0; strm_idx<strm_cnt; ++strm_idx) {
1548 pjmedia_port *aud;
1549 pjmedia_format *fmt;
1550 pjsua_conf_port_id slot;
1551 char fmt_name[5];
1552
1553 aud = pjmedia_avi_streams_get_stream(avdp.avi_streams,
1554 strm_idx);
1555 fmt = &aud->info.fmt;
1556
1557 pjmedia_fourcc_name(fmt->id, fmt_name);
1558
1559 if (fmt->id == PJMEDIA_FORMAT_PCM) {
1560 status = pjsua_conf_add_port(app_config.pool, aud,
1561 &slot);
1562 if (status == PJ_SUCCESS) {
1563 PJ_LOG(4,(THIS_FILE,
1564 "AVI %.*s: audio added to slot %d",
1565 (int)avdp.title.slen, avdp.title.ptr,
1566 slot));
1567 app_config.avi[i].slot = slot;
1568 }
1569 } else {
1570 PJ_LOG(4,(THIS_FILE,
1571 "AVI %.*s: audio ignored, format=%s",
1572 (int)avdp.title.slen, avdp.title.ptr,
1573 fmt_name));
1574 }
1575 }
1576 }
1577#else
1578 PJ_LOG(2,(THIS_FILE,
1579 "Warning: --play-avi is ignored because AVI is disabled"));
1580#endif /* PJMEDIA_VIDEO_DEV_HAS_AVI */
1581 }
1582
1583 /* Add UDP transport unless it's disabled. */
1584 if (!app_config.no_udp) {
1585 pjsua_acc_id aid;
1586 pjsip_transport_type_e type = PJSIP_TRANSPORT_UDP;
1587
1588 status = pjsua_transport_create(type,
1589 &app_config.udp_cfg,
1590 &transport_id);
1591 if (status != PJ_SUCCESS)
1592 goto on_error;
1593
1594 /* Add local account */
1595 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1596
1597 /* Adjust local account config based on pjsua app config */
1598 {
1599 pjsua_acc_config acc_cfg;
1600 pjsua_acc_get_config(aid, tmp_pool, &acc_cfg);
1601
1602 app_config_init_video(&acc_cfg);
1603 acc_cfg.rtp_cfg = app_config.rtp_cfg;
1604 pjsua_acc_modify(aid, &acc_cfg);
1605 }
1606
1607 //pjsua_acc_set_transport(aid, transport_id);
1608 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1609
1610 if (app_config.udp_cfg.port == 0) {
1611 pjsua_transport_info ti;
1612 pj_sockaddr_in *a;
1613
1614 pjsua_transport_get_info(transport_id, &ti);
1615 a = (pj_sockaddr_in*)&ti.local_addr;
1616
1617 tcp_cfg.port = pj_ntohs(a->sin_port);
1618 }
1619 }
1620
1621 /* Add UDP IPv6 transport unless it's disabled. */
1622 if (!app_config.no_udp && app_config.ipv6) {
1623 pjsua_acc_id aid;
1624 pjsip_transport_type_e type = PJSIP_TRANSPORT_UDP6;
1625 pjsua_transport_config udp_cfg;
1626
1627 udp_cfg = app_config.udp_cfg;
1628 if (udp_cfg.port == 0)
1629 udp_cfg.port = 5060;
1630 else
1631 udp_cfg.port += 10;
1632 status = pjsua_transport_create(type,
1633 &udp_cfg,
1634 &transport_id);
1635 if (status != PJ_SUCCESS)
1636 goto on_error;
1637
1638 /* Add local account */
1639 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1640
1641 /* Adjust local account config based on pjsua app config */
1642 {
1643 pjsua_acc_config acc_cfg;
1644 pjsua_acc_get_config(aid, tmp_pool, &acc_cfg);
1645
1646 app_config_init_video(&acc_cfg);
1647 acc_cfg.rtp_cfg = app_config.rtp_cfg;
1648 acc_cfg.ipv6_media_use = PJSUA_IPV6_ENABLED;
1649 pjsua_acc_modify(aid, &acc_cfg);
1650 }
1651
1652 //pjsua_acc_set_transport(aid, transport_id);
1653 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1654
1655 if (app_config.udp_cfg.port == 0) {
1656 pjsua_transport_info ti;
1657 pj_sockaddr_in *a;
1658
1659 pjsua_transport_get_info(transport_id, &ti);
1660 a = (pj_sockaddr_in*)&ti.local_addr;
1661
1662 tcp_cfg.port = pj_ntohs(a->sin_port);
1663 }
1664 }
1665
1666 /* Add TCP transport unless it's disabled */
1667 if (!app_config.no_tcp) {
1668 pjsua_acc_id aid;
1669
1670 status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
1671 &tcp_cfg,
1672 &transport_id);
1673 if (status != PJ_SUCCESS)
1674 goto on_error;
1675
1676 /* Add local account */
1677 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1678
1679 /* Adjust local account config based on pjsua app config */
1680 {
1681 pjsua_acc_config acc_cfg;
1682 pjsua_acc_get_config(aid, tmp_pool, &acc_cfg);
1683
1684 app_config_init_video(&acc_cfg);
1685 acc_cfg.rtp_cfg = app_config.rtp_cfg;
1686 pjsua_acc_modify(aid, &acc_cfg);
1687 }
1688
1689 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1690
1691 }
1692
1693 /* Add TCP IPv6 transport unless it's disabled. */
1694 if (!app_config.no_tcp && app_config.ipv6) {
1695 pjsua_acc_id aid;
1696 pjsip_transport_type_e type = PJSIP_TRANSPORT_TCP6;
1697
1698 tcp_cfg.port += 10;
1699
1700 status = pjsua_transport_create(type,
1701 &tcp_cfg,
1702 &transport_id);
1703 if (status != PJ_SUCCESS)
1704 goto on_error;
1705
1706 /* Add local account */
1707 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1708
1709 /* Adjust local account config based on pjsua app config */
1710 {
1711 pjsua_acc_config acc_cfg;
1712 pjsua_acc_get_config(aid, tmp_pool, &acc_cfg);
1713
1714 app_config_init_video(&acc_cfg);
1715 acc_cfg.rtp_cfg = app_config.rtp_cfg;
1716 acc_cfg.ipv6_media_use = PJSUA_IPV6_ENABLED;
1717 pjsua_acc_modify(aid, &acc_cfg);
1718 }
1719
1720 //pjsua_acc_set_transport(aid, transport_id);
1721 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1722 }
1723
1724
1725#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
1726 /* Add TLS transport when application wants one */
1727 if (app_config.use_tls) {
1728
1729 pjsua_acc_id acc_id;
1730
1731 /* Copy the QoS settings */
1732 tcp_cfg.tls_setting.qos_type = tcp_cfg.qos_type;
1733 pj_memcpy(&tcp_cfg.tls_setting.qos_params, &tcp_cfg.qos_params,
1734 sizeof(tcp_cfg.qos_params));
1735
1736 /* Set TLS port as TCP port+1 */
1737 tcp_cfg.port++;
1738 status = pjsua_transport_create(PJSIP_TRANSPORT_TLS,
1739 &tcp_cfg,
1740 &transport_id);
1741 tcp_cfg.port--;
1742 if (status != PJ_SUCCESS)
1743 goto on_error;
1744
1745 /* Add local account */
1746 pjsua_acc_add_local(transport_id, PJ_FALSE, &acc_id);
1747
1748 /* Adjust local account config based on pjsua app config */
1749 {
1750 pjsua_acc_config acc_cfg;
1751 pjsua_acc_get_config(acc_id, tmp_pool, &acc_cfg);
1752
1753 app_config_init_video(&acc_cfg);
1754 acc_cfg.rtp_cfg = app_config.rtp_cfg;
1755 pjsua_acc_modify(acc_id, &acc_cfg);
1756 }
1757
1758 pjsua_acc_set_online_status(acc_id, PJ_TRUE);
1759 }
1760
1761 /* Add TLS IPv6 transport unless it's disabled. */
1762 if (app_config.use_tls && app_config.ipv6) {
1763 pjsua_acc_id aid;
1764 pjsip_transport_type_e type = PJSIP_TRANSPORT_TLS6;
1765
1766 tcp_cfg.port += 10;
1767
1768 status = pjsua_transport_create(type,
1769 &tcp_cfg,
1770 &transport_id);
1771 if (status != PJ_SUCCESS)
1772 goto on_error;
1773
1774 /* Add local account */
1775 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
1776
1777 /* Adjust local account config based on pjsua app config */
1778 {
1779 pjsua_acc_config acc_cfg;
1780 pjsua_acc_get_config(aid, tmp_pool, &acc_cfg);
1781
1782 app_config_init_video(&acc_cfg);
1783 acc_cfg.rtp_cfg = app_config.rtp_cfg;
1784 acc_cfg.ipv6_media_use = PJSUA_IPV6_ENABLED;
1785 pjsua_acc_modify(aid, &acc_cfg);
1786 }
1787
1788 //pjsua_acc_set_transport(aid, transport_id);
1789 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1790 }
1791
1792#endif
1793
1794 if (transport_id == -1) {
1795 PJ_LOG(1,(THIS_FILE, "Error: no transport is configured"));
1796 status = -1;
1797 goto on_error;
1798 }
1799
1800
1801 /* Add accounts */
1802 for (i=0; i<app_config.acc_cnt; ++i) {
1803 app_config.acc_cfg[i].rtp_cfg = app_config.rtp_cfg;
1804 app_config.acc_cfg[i].reg_retry_interval = 300;
1805 app_config.acc_cfg[i].reg_first_retry_interval = 60;
1806
1807 app_config_init_video(&app_config.acc_cfg[i]);
1808
1809 status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL);
1810 if (status != PJ_SUCCESS)
1811 goto on_error;
1812 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
1813 }
1814
1815 /* Add buddies */
1816 for (i=0; i<app_config.buddy_cnt; ++i) {
1817 status = pjsua_buddy_add(&app_config.buddy_cfg[i], NULL);
1818 if (status != PJ_SUCCESS) {
1819 PJ_PERROR(1,(THIS_FILE, status, "Error adding buddy"));
1820 goto on_error;
1821 }
1822 }
1823
1824 /* Optionally disable some codec */
1825 for (i=0; i<app_config.codec_dis_cnt; ++i) {
1826 pjsua_codec_set_priority(&app_config.codec_dis[i],
1827 PJMEDIA_CODEC_PRIO_DISABLED);
1828#if PJSUA_HAS_VIDEO
1829 pjsua_vid_codec_set_priority(&app_config.codec_dis[i],
1830 PJMEDIA_CODEC_PRIO_DISABLED);
1831#endif
1832 }
1833
1834 /* Optionally set codec orders */
1835 for (i=0; i<app_config.codec_cnt; ++i) {
1836 pjsua_codec_set_priority(&app_config.codec_arg[i],
1837 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
1838#if PJSUA_HAS_VIDEO
1839 pjsua_vid_codec_set_priority(&app_config.codec_arg[i],
1840 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
1841#endif
1842 }
1843
1844 /* Use null sound device? */
1845#ifndef STEREO_DEMO
1846 if (app_config.null_audio) {
1847 status = pjsua_set_null_snd_dev();
1848 if (status != PJ_SUCCESS)
1849 return status;
1850 }
1851#endif
1852
1853 if (app_config.capture_dev != PJSUA_INVALID_ID ||
1854 app_config.playback_dev != PJSUA_INVALID_ID)
1855 {
1856 status = pjsua_set_snd_dev(app_config.capture_dev,
1857 app_config.playback_dev);
1858 if (status != PJ_SUCCESS)
1859 goto on_error;
1860 }
1861
1862 /* Init call setting */
1863 pjsua_call_setting_default(&call_opt);
1864 call_opt.aud_cnt = app_config.aud_cnt;
1865 call_opt.vid_cnt = app_config.vid.vid_cnt;
1866
1867 pj_pool_release(tmp_pool);
1868 return PJ_SUCCESS;
1869
1870on_error:
1871 pj_pool_release(tmp_pool);
1872 app_destroy();
1873 return status;
1874}
1875
1876pj_status_t pjsua_app_init(const pjsua_app_cfg_t *cfg)
1877{
1878 pj_status_t status;
1879 pj_memcpy(&app_cfg, cfg, sizeof(app_cfg));
1880
1881 status = app_init();
1882 if (status != PJ_SUCCESS)
1883 return status;
1884
1885 /* Init CLI if configured */
1886 if (app_config.use_cli) {
1887 status = cli_init();
1888 }
1889 return status;
1890}
1891
1892pj_status_t pjsua_app_run(pj_bool_t wait_telnet_cli)
1893{
1894 pj_thread_t *stdout_refresh_thread = NULL;
1895 pj_status_t status;
1896
1897 /* Start console refresh thread */
1898 if (stdout_refresh > 0) {
1899 pj_thread_create(app_config.pool, "stdout", &stdout_refresh_proc,
1900 NULL, 0, 0, &stdout_refresh_thread);
1901 }
1902
1903 status = pjsua_start();
1904 if (status != PJ_SUCCESS)
1905 goto on_return;
1906
1907 if (app_config.use_cli && (app_config.cli_cfg.cli_fe & CLI_FE_TELNET)) {
1908 char info[128];
1909 cli_get_info(info, sizeof(info));
1910 if (app_cfg.on_started) {
1911 (*app_cfg.on_started)(status, info);
1912 }
1913 } else {
1914 if (app_cfg.on_started) {
1915 (*app_cfg.on_started)(status, "Ready");
1916 }
1917 }
1918
1919 /* If user specifies URI to call, then call the URI */
1920 if (uri_arg.slen) {
1921 pjsua_call_setting_default(&call_opt);
1922 call_opt.aud_cnt = app_config.aud_cnt;
1923 call_opt.vid_cnt = app_config.vid.vid_cnt;
1924
1925 pjsua_call_make_call(current_acc, &uri_arg, &call_opt, NULL,
1926 NULL, NULL);
1927 }
1928
1929 app_running = PJ_TRUE;
1930
1931 if (app_config.use_cli)
1932 cli_main(wait_telnet_cli);
1933 else
1934 legacy_main();
1935
1936 status = PJ_SUCCESS;
1937
1938on_return:
1939 if (stdout_refresh_thread) {
1940 stdout_refresh_quit = PJ_TRUE;
1941 pj_thread_join(stdout_refresh_thread);
1942 pj_thread_destroy(stdout_refresh_thread);
1943 stdout_refresh_quit = PJ_FALSE;
1944 }
1945 return status;
1946}
1947
1948static pj_status_t app_destroy()
1949{
1950 pj_status_t status = PJ_SUCCESS;
1951 unsigned i;
1952 pj_bool_t use_cli = PJ_FALSE;
1953 int cli_fe = 0;
1954 pj_uint16_t cli_telnet_port = 0;
1955
1956#ifdef STEREO_DEMO
1957 if (app_config.snd) {
1958 pjmedia_snd_port_destroy(app_config.snd);
1959 app_config.snd = NULL;
1960 }
1961 if (app_config.sc_ch1) {
1962 pjsua_conf_remove_port(app_config.sc_ch1_slot);
1963 app_config.sc_ch1_slot = PJSUA_INVALID_ID;
1964 pjmedia_port_destroy(app_config.sc_ch1);
1965 app_config.sc_ch1 = NULL;
1966 }
1967 if (app_config.sc) {
1968 pjmedia_port_destroy(app_config.sc);
1969 app_config.sc = NULL;
1970 }
1971#endif
1972
1973 /* Close avi devs and ports */
1974 for (i=0; i<app_config.avi_cnt; ++i) {
1975 if (app_config.avi[i].slot != PJSUA_INVALID_ID)
1976 pjsua_conf_remove_port(app_config.avi[i].slot);
1977#if PJMEDIA_HAS_VIDEO && PJMEDIA_VIDEO_DEV_HAS_AVI
1978 if (app_config.avi[i].dev_id != PJMEDIA_VID_INVALID_DEV)
1979 pjmedia_avi_dev_free(app_config.avi[i].dev_id);
1980#endif
1981 }
1982
1983 /* Close ringback port */
1984 if (app_config.ringback_port &&
1985 app_config.ringback_slot != PJSUA_INVALID_ID)
1986 {
1987 pjsua_conf_remove_port(app_config.ringback_slot);
1988 app_config.ringback_slot = PJSUA_INVALID_ID;
1989 pjmedia_port_destroy(app_config.ringback_port);
1990 app_config.ringback_port = NULL;
1991 }
1992
1993 /* Close ring port */
1994 if (app_config.ring_port && app_config.ring_slot != PJSUA_INVALID_ID) {
1995 pjsua_conf_remove_port(app_config.ring_slot);
1996 app_config.ring_slot = PJSUA_INVALID_ID;
1997 pjmedia_port_destroy(app_config.ring_port);
1998 app_config.ring_port = NULL;
1999 }
2000
2001 /* Close tone generators */
2002 for (i=0; i<app_config.tone_count; ++i) {
2003 pjsua_conf_remove_port(app_config.tone_slots[i]);
2004 }
2005
2006 if (app_config.pool) {
2007 pj_pool_release(app_config.pool);
2008 app_config.pool = NULL;
2009 }
2010
2011 status = pjsua_destroy();
2012
2013 if (app_config.use_cli) {
2014 use_cli = app_config.use_cli;
2015 cli_fe = app_config.cli_cfg.cli_fe;
2016 cli_telnet_port = app_config.cli_cfg.telnet_cfg.port;
2017 }
2018
2019 /* Reset config */
2020 pj_bzero(&app_config, sizeof(app_config));
2021
2022 if (use_cli) {
2023 app_config.use_cli = use_cli;
2024 app_config.cli_cfg.cli_fe = cli_fe;
2025 app_config.cli_cfg.telnet_cfg.port = cli_telnet_port;
2026 }
2027
2028 return status;
2029}
2030
2031pj_status_t pjsua_app_destroy()
2032{
2033 pj_status_t status;
2034
2035 status = app_destroy();
2036
2037 if (app_config.use_cli) {
2038 cli_destroy();
2039 }
2040
2041 return status;
2042}
2043
2044/** ======================= **/
2045
2046#ifdef STEREO_DEMO
2047/*
2048 * In this stereo demo, we open the sound device in stereo mode and
2049 * arrange the attachment to the PJSUA-LIB conference bridge as such
2050 * so that channel0/left channel of the sound device corresponds to
2051 * slot 0 in the bridge, and channel1/right channel of the sound
2052 * device corresponds to slot 1 in the bridge. Then user can independently
2053 * feed different media to/from the speakers/microphones channels, by
2054 * connecting them to slot 0 or 1 respectively.
2055 *
2056 * Here's how the connection looks like:
2057 *
2058 +-----------+ stereo +-----------------+ 2x mono +-----------+
2059 | AUDIO DEV |<------>| SPLITCOMB left|<------->|#0 BRIDGE |
2060 +-----------+ | right|<------->|#1 |
2061 +-----------------+ +-----------+
2062 */
2063static void stereo_demo()
2064{
2065 pjmedia_port *conf;
2066 pj_status_t status;
2067
2068 /* Disable existing sound device */
2069 conf = pjsua_set_no_snd_dev();
2070
2071 /* Create stereo-mono splitter/combiner */
2072 status = pjmedia_splitcomb_create(app_config.pool,
2073 conf->info.clock_rate /* clock rate */,
2074 2 /* stereo */,
2075 2 * conf->info.samples_per_frame,
2076 conf->info.bits_per_sample,
2077 0 /* options */,
2078 &app_config.sc);
2079 pj_assert(status == PJ_SUCCESS);
2080
2081 /* Connect channel0 (left channel?) to conference port slot0 */
2082 status = pjmedia_splitcomb_set_channel(app_config.sc, 0 /* ch0 */,
2083 0 /*options*/,
2084 conf);
2085 pj_assert(status == PJ_SUCCESS);
2086
2087 /* Create reverse channel for channel1 (right channel?)... */
2088 status = pjmedia_splitcomb_create_rev_channel(app_config.pool,
2089 app_config.sc,
2090 1 /* ch1 */,
2091 0 /* options */,
2092 &app_config.sc_ch1);
2093 pj_assert(status == PJ_SUCCESS);
2094
2095 /* .. and register it to conference bridge (it would be slot1
2096 * if there's no other devices connected to the bridge)
2097 */
2098 status = pjsua_conf_add_port(app_config.pool, app_config.sc_ch1,
2099 &app_config.sc_ch1_slot);
2100 pj_assert(status == PJ_SUCCESS);
2101
2102 /* Create sound device */
2103 status = pjmedia_snd_port_create(app_config.pool, -1, -1,
2104 conf->info.clock_rate,
2105 2 /* stereo */,
2106 2 * conf->info.samples_per_frame,
2107 conf->info.bits_per_sample,
2108 0, &app_config.snd);
2109 pj_assert(status == PJ_SUCCESS);
2110
2111
2112 /* Connect the splitter to the sound device */
2113 status = pjmedia_snd_port_connect(app_config.snd, app_config.sc);
2114 pj_assert(status == PJ_SUCCESS);
2115}
2116#endif