blob: b40cb7e4c68e2aa27b97caa1a732330351ff3758 [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
Benny Prijono844653c2008-12-23 17:27:53 +00003 * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
Benny Prijono32177c02008-06-20 22:44:47 +00004 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
Benny Prijonoeebe9af2006-06-13 22:57:13 +00005 *
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-lib/pjsua.h>
21#include <pjsua-lib/pjsua_internal.h>
22
23
24#define THIS_FILE "pjsua_media.c"
25
Nanang Izzuddin68559c32008-06-13 17:01:46 +000026#define DEFAULT_RTP_PORT 4000
27
28#define NULL_SND_DEV_ID -99
Benny Prijono80eee892007-11-03 22:43:23 +000029
30#ifndef PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
31# define PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT 0
32#endif
33
Benny Prijonoeebe9af2006-06-13 22:57:13 +000034
Benny Prijonode479562007-03-15 10:23:55 +000035/* Next RTP port to be used */
36static pj_uint16_t next_rtp_port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000037
Benny Prijonof798e502009-03-09 13:08:16 +000038/* Open sound dev */
39static pj_status_t open_snd_dev(pjmedia_aud_param *param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000040/* Close existing sound device */
41static void close_snd_dev(void);
Benny Prijonof798e502009-03-09 13:08:16 +000042/* Create audio device param */
43static pj_status_t create_aud_param(pjmedia_aud_param *param,
44 pjmedia_aud_dev_index capture_dev,
45 pjmedia_aud_dev_index playback_dev,
46 unsigned clock_rate,
47 unsigned channel_count,
48 unsigned samples_per_frame,
49 unsigned bits_per_sample);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000050
51
Benny Prijonof76e1392008-06-06 14:51:48 +000052static void pjsua_media_config_dup(pj_pool_t *pool,
53 pjsua_media_config *dst,
54 const pjsua_media_config *src)
55{
56 pj_memcpy(dst, src, sizeof(*src));
57 pj_strdup(pool, &dst->turn_server, &src->turn_server);
58 pj_stun_auth_cred_dup(pool, &dst->turn_auth_cred, &src->turn_auth_cred);
59}
60
Nanang Izzuddin50fae732011-03-22 09:49:23 +000061
Benny Prijonoeebe9af2006-06-13 22:57:13 +000062/**
63 * Init media subsystems.
64 */
65pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
66{
Benny Prijonoba5926a2007-05-02 11:29:37 +000067 pj_str_t codec_id = {NULL, 0};
Benny Prijono0498d902006-06-19 14:49:14 +000068 unsigned opt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000069 pj_status_t status;
70
Benny Prijonofc24e692007-01-27 18:31:51 +000071 /* To suppress warning about unused var when all codecs are disabled */
72 PJ_UNUSED_ARG(codec_id);
73
Benny Prijonof798e502009-03-09 13:08:16 +000074 /* Specify which audio device settings are save-able */
75 pjsua_var.aud_svmask = 0xFFFFFFFF;
76 /* These are not-settable */
77 pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
78 PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER |
79 PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER);
Benny Prijonoe506c8c2009-03-10 13:28:43 +000080 /* EC settings use different API */
81 pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EC |
82 PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijonof798e502009-03-09 13:08:16 +000083
Benny Prijonoeebe9af2006-06-13 22:57:13 +000084 /* Copy configuration */
Benny Prijonof76e1392008-06-06 14:51:48 +000085 pjsua_media_config_dup(pjsua_var.pool, &pjsua_var.media_cfg, cfg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000086
87 /* Normalize configuration */
Benny Prijono50f19b32008-03-11 13:15:43 +000088 if (pjsua_var.media_cfg.snd_clock_rate == 0) {
89 pjsua_var.media_cfg.snd_clock_rate = pjsua_var.media_cfg.clock_rate;
90 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +000091
92 if (pjsua_var.media_cfg.has_ioqueue &&
93 pjsua_var.media_cfg.thread_cnt == 0)
94 {
95 pjsua_var.media_cfg.thread_cnt = 1;
96 }
97
98 if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
99 pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
100 }
101
102 /* Create media endpoint. */
103 status = pjmedia_endpt_create(&pjsua_var.cp.factory,
104 pjsua_var.media_cfg.has_ioqueue? NULL :
105 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
106 pjsua_var.media_cfg.thread_cnt,
107 &pjsua_var.med_endpt);
108 if (status != PJ_SUCCESS) {
109 pjsua_perror(THIS_FILE,
110 "Media stack initialization has returned error",
111 status);
112 return status;
113 }
114
115 /* Register all codecs */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000116
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000117#if PJMEDIA_HAS_SPEEX_CODEC
118 /* Register speex. */
Nanang Izzuddin9dbad152008-06-10 18:56:10 +0000119 status = pjmedia_codec_speex_init(pjsua_var.med_endpt,
120 0,
121 pjsua_var.media_cfg.quality,
122 -1);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000123 if (status != PJ_SUCCESS) {
124 pjsua_perror(THIS_FILE, "Error initializing Speex codec",
125 status);
126 return status;
127 }
Benny Prijono7ca96da2006-08-07 12:11:40 +0000128
129 /* Set speex/16000 to higher priority*/
130 codec_id = pj_str("speex/16000");
131 pjmedia_codec_mgr_set_codec_priority(
132 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
133 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
134
135 /* Set speex/8000 to next higher priority*/
136 codec_id = pj_str("speex/8000");
137 pjmedia_codec_mgr_set_codec_priority(
138 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
139 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
140
141
142
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000143#endif /* PJMEDIA_HAS_SPEEX_CODEC */
144
Benny Prijono00cae612006-07-31 15:19:36 +0000145#if PJMEDIA_HAS_ILBC_CODEC
146 /* Register iLBC. */
147 status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt,
148 pjsua_var.media_cfg.ilbc_mode);
149 if (status != PJ_SUCCESS) {
150 pjsua_perror(THIS_FILE, "Error initializing iLBC codec",
151 status);
152 return status;
153 }
154#endif /* PJMEDIA_HAS_ILBC_CODEC */
155
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000156#if PJMEDIA_HAS_GSM_CODEC
157 /* Register GSM */
158 status = pjmedia_codec_gsm_init(pjsua_var.med_endpt);
159 if (status != PJ_SUCCESS) {
160 pjsua_perror(THIS_FILE, "Error initializing GSM codec",
161 status);
162 return status;
163 }
164#endif /* PJMEDIA_HAS_GSM_CODEC */
165
166#if PJMEDIA_HAS_G711_CODEC
167 /* Register PCMA and PCMU */
168 status = pjmedia_codec_g711_init(pjsua_var.med_endpt);
169 if (status != PJ_SUCCESS) {
170 pjsua_perror(THIS_FILE, "Error initializing G711 codec",
171 status);
172 return status;
173 }
174#endif /* PJMEDIA_HAS_G711_CODEC */
175
Benny Prijono7ffd7752008-03-17 14:07:53 +0000176#if PJMEDIA_HAS_G722_CODEC
177 status = pjmedia_codec_g722_init( pjsua_var.med_endpt );
178 if (status != PJ_SUCCESS) {
179 pjsua_perror(THIS_FILE, "Error initializing G722 codec",
180 status);
181 return status;
182 }
183#endif /* PJMEDIA_HAS_G722_CODEC */
184
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000185#if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000186 /* Register IPP codecs */
187 status = pjmedia_codec_ipp_init(pjsua_var.med_endpt);
188 if (status != PJ_SUCCESS) {
189 pjsua_perror(THIS_FILE, "Error initializing IPP codecs",
190 status);
191 return status;
192 }
193
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000194#endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000195
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000196#if PJMEDIA_HAS_PASSTHROUGH_CODECS
197 /* Register passthrough codecs */
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000198 {
199 unsigned aud_idx;
200 unsigned ext_fmt_cnt = 0;
201 pjmedia_format ext_fmts[32];
202 pjmedia_codec_passthrough_setting setting;
203
204 /* List extended formats supported by audio devices */
205 for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) {
206 pjmedia_aud_dev_info aud_info;
207 unsigned i;
208
209 status = pjmedia_aud_dev_get_info(aud_idx, &aud_info);
210 if (status != PJ_SUCCESS) {
211 pjsua_perror(THIS_FILE, "Error querying audio device info",
212 status);
213 return status;
214 }
215
216 /* Collect extended formats supported by this audio device */
217 for (i = 0; i < aud_info.ext_fmt_cnt; ++i) {
218 unsigned j;
219 pj_bool_t is_listed = PJ_FALSE;
220
221 /* See if this extended format is already in the list */
222 for (j = 0; j < ext_fmt_cnt && !is_listed; ++j) {
223 if (ext_fmts[j].id == aud_info.ext_fmt[i].id &&
224 ext_fmts[j].bitrate == aud_info.ext_fmt[i].bitrate)
225 {
226 is_listed = PJ_TRUE;
227 }
228 }
229
230 /* Put this format into the list, if it is not in the list */
231 if (!is_listed)
232 ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i];
233
234 pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts));
235 }
236 }
237
238 /* Init the passthrough codec with supported formats only */
239 setting.fmt_cnt = ext_fmt_cnt;
240 setting.fmts = ext_fmts;
Nanang Izzuddin873f3e42009-07-15 17:55:16 +0000241 setting.ilbc_mode = cfg->ilbc_mode;
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000242 status = pjmedia_codec_passthrough_init2(pjsua_var.med_endpt, &setting);
243 if (status != PJ_SUCCESS) {
244 pjsua_perror(THIS_FILE, "Error initializing passthrough codecs",
245 status);
246 return status;
247 }
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000248 }
249#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
250
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000251#if PJMEDIA_HAS_G7221_CODEC
252 /* Register G722.1 codecs */
253 status = pjmedia_codec_g7221_init(pjsua_var.med_endpt);
254 if (status != PJ_SUCCESS) {
255 pjsua_perror(THIS_FILE, "Error initializing G722.1 codec",
256 status);
257 return status;
258 }
259#endif /* PJMEDIA_HAS_G7221_CODEC */
260
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000261#if PJMEDIA_HAS_L16_CODEC
262 /* Register L16 family codecs, but disable all */
263 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
264 if (status != PJ_SUCCESS) {
265 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
266 status);
267 return status;
268 }
269
270 /* Disable ALL L16 codecs */
271 codec_id = pj_str("L16");
272 pjmedia_codec_mgr_set_codec_priority(
273 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
274 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
275
276#endif /* PJMEDIA_HAS_L16_CODEC */
277
278
279 /* Save additional conference bridge parameters for future
280 * reference.
281 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000282 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000283 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000284 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
285 pjsua_var.mconf_cfg.channel_count *
286 pjsua_var.media_cfg.audio_frame_ptime /
287 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000288
Benny Prijono0498d902006-06-19 14:49:14 +0000289 /* Init options for conference bridge. */
290 opt = PJMEDIA_CONF_NO_DEVICE;
291 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000292 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000293 {
294 opt |= PJMEDIA_CONF_SMALL_FILTER;
295 }
296 else if (pjsua_var.media_cfg.quality < 3) {
297 opt |= PJMEDIA_CONF_USE_LINEAR;
298 }
299
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000300 /* Init conference bridge. */
301 status = pjmedia_conf_create(pjsua_var.pool,
302 pjsua_var.media_cfg.max_media_ports,
303 pjsua_var.media_cfg.clock_rate,
304 pjsua_var.mconf_cfg.channel_count,
305 pjsua_var.mconf_cfg.samples_per_frame,
306 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000307 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000308 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000309 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000310 status);
311 return status;
312 }
313
Benny Prijonof798e502009-03-09 13:08:16 +0000314 /* Are we using the audio switchboard (a.k.a APS-Direct)? */
315 pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
316 ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
317
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000318 /* Create null port just in case user wants to use null sound. */
319 status = pjmedia_null_port_create(pjsua_var.pool,
320 pjsua_var.media_cfg.clock_rate,
321 pjsua_var.mconf_cfg.channel_count,
322 pjsua_var.mconf_cfg.samples_per_frame,
323 pjsua_var.mconf_cfg.bits_per_sample,
324 &pjsua_var.null_port);
325 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
326
Nanang Izzuddin69b69ae2009-04-14 15:18:30 +0000327#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
328 /* Initialize SRTP library. */
329 status = pjmedia_srtp_init_lib();
330 if (status != PJ_SUCCESS) {
331 pjsua_perror(THIS_FILE, "Error initializing SRTP library",
332 status);
333 return status;
334 }
335#endif
336
Benny Prijono9f468d12011-07-07 07:46:33 +0000337 /* Video */
338#if PJMEDIA_HAS_VIDEO
339 status = pjsua_vid_subsys_init();
340 if (status != PJ_SUCCESS)
341 return status;
342#endif
343
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000344 return PJ_SUCCESS;
345}
346
347
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000348/* Check if sound device is idle. */
349static void check_snd_dev_idle()
350{
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000351 unsigned call_cnt;
352
353 /* Get the call count, we shouldn't close the sound device when there is
354 * any calls active.
355 */
356 call_cnt = pjsua_call_get_count();
357
358 /* When this function is called from pjsua_media_channel_deinit() upon
359 * disconnecting call, actually the call count hasn't been updated/
360 * decreased. So we put additional check here, if there is only one
361 * call and it's in DISCONNECTED state, there is actually no active
362 * call.
363 */
364 if (call_cnt == 1) {
365 pjsua_call_id call_id;
366 pj_status_t status;
367
368 status = pjsua_enum_calls(&call_id, &call_cnt);
369 if (status == PJ_SUCCESS && call_cnt > 0 &&
370 !pjsua_call_is_active(call_id))
371 {
372 call_cnt = 0;
373 }
374 }
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000375
376 /* Activate sound device auto-close timer if sound device is idle.
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000377 * It is idle when there is no port connection in the bridge and
378 * there is no active call.
Benny Prijono2d647722011-07-13 03:05:22 +0000379 *
380 * Note: this block is now valid if no snd dev is used because of #1299
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000381 */
Benny Prijono2d647722011-07-13 03:05:22 +0000382 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL ||
383 pjsua_var.no_snd) &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000384 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
385 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000386 call_cnt == 0 &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000387 pjsua_var.media_cfg.snd_auto_close_time >= 0)
388 {
389 pj_time_val delay;
390
391 delay.msec = 0;
392 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
393
394 pjsua_var.snd_idle_timer.id = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000395 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000396 &delay);
397 }
398}
399
400
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000401/* Timer callback to close sound device */
402static void close_snd_timer_cb( pj_timer_heap_t *th,
403 pj_timer_entry *entry)
404{
405 PJ_UNUSED_ARG(th);
406
Benny Prijono0f711b42009-05-06 19:08:43 +0000407 PJSUA_LOCK();
408 if (entry->id) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000409 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
Benny Prijono0f711b42009-05-06 19:08:43 +0000410 pjsua_var.media_cfg.snd_auto_close_time));
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000411
Benny Prijono0f711b42009-05-06 19:08:43 +0000412 entry->id = PJ_FALSE;
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000413
Benny Prijono0f711b42009-05-06 19:08:43 +0000414 close_snd_dev();
415 }
416 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000417}
418
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000419
420/*
421 * Start pjsua media subsystem.
422 */
423pj_status_t pjsua_media_subsys_start(void)
424{
425 pj_status_t status;
426
Benny Prijono0bc99a92011-03-17 04:34:43 +0000427#if DISABLED_FOR_TICKET_1185
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000428 /* Create media for calls, if none is specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000429 if (pjsua_var.calls[0].media[0].tp == NULL) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000430 pjsua_transport_config transport_cfg;
431
432 /* Create default transport config */
433 pjsua_transport_config_default(&transport_cfg);
434 transport_cfg.port = DEFAULT_RTP_PORT;
435
436 status = pjsua_media_transports_create(&transport_cfg);
437 if (status != PJ_SUCCESS)
438 return status;
439 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000440#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000441
Benny Prijono0bc99a92011-03-17 04:34:43 +0000442 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000443 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000444
Benny Prijono9f468d12011-07-07 07:46:33 +0000445 /* Video */
446#if PJMEDIA_HAS_VIDEO
447 status = pjsua_vid_subsys_start();
448 if (status != PJ_SUCCESS)
449 return status;
450#endif
451
Benny Prijonobf53b002010-01-04 13:08:31 +0000452 /* Perform NAT detection */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000453 status = pjsua_detect_nat_type();
454 if (status != PJ_SUCCESS) {
455 PJ_PERROR(1,(THIS_FILE, status, "NAT type detection failed"));
456 }
Benny Prijonobf53b002010-01-04 13:08:31 +0000457
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000458 return PJ_SUCCESS;
459}
460
461
462/*
463 * Destroy pjsua media subsystem.
464 */
465pj_status_t pjsua_media_subsys_destroy(void)
466{
467 unsigned i;
468
Benny Prijono384dab42009-10-14 01:58:04 +0000469 PJ_LOG(4,(THIS_FILE, "Shutting down media.."));
470
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000471 close_snd_dev();
472
473 if (pjsua_var.mconf) {
474 pjmedia_conf_destroy(pjsua_var.mconf);
475 pjsua_var.mconf = NULL;
476 }
477
478 if (pjsua_var.null_port) {
479 pjmedia_port_destroy(pjsua_var.null_port);
480 pjsua_var.null_port = NULL;
481 }
482
483 /* Destroy file players */
484 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
485 if (pjsua_var.player[i].port) {
486 pjmedia_port_destroy(pjsua_var.player[i].port);
487 pjsua_var.player[i].port = NULL;
488 }
489 }
490
491 /* Destroy file recorders */
492 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
493 if (pjsua_var.recorder[i].port) {
494 pjmedia_port_destroy(pjsua_var.recorder[i].port);
495 pjsua_var.recorder[i].port = NULL;
496 }
497 }
498
499 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000500 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000501 unsigned strm_idx;
502 pjsua_call *call = &pjsua_var.calls[i];
503 for (strm_idx=0; strm_idx<call->med_cnt; ++strm_idx) {
504 pjsua_call_media *call_med = &call->media[strm_idx];
505 if (call_med->tp_st != PJSUA_MED_TP_IDLE) {
506 pjsua_media_channel_deinit(i);
507 }
508 if (call_med->tp && call_med->tp_auto_del) {
509 pjmedia_transport_close(call_med->tp);
510 }
511 call_med->tp = NULL;
Benny Prijono311b63f2008-07-14 11:31:40 +0000512 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000513 }
514
515 /* Destroy media endpoint. */
516 if (pjsua_var.med_endpt) {
517
Benny Prijono0bc99a92011-03-17 04:34:43 +0000518# if PJMEDIA_HAS_VIDEO
Benny Prijono9f468d12011-07-07 07:46:33 +0000519 pjsua_vid_subsys_destroy();
Benny Prijono0bc99a92011-03-17 04:34:43 +0000520# endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000521 /* Shutdown all codecs: */
522# if PJMEDIA_HAS_SPEEX_CODEC
523 pjmedia_codec_speex_deinit();
524# endif /* PJMEDIA_HAS_SPEEX_CODEC */
525
526# if PJMEDIA_HAS_GSM_CODEC
527 pjmedia_codec_gsm_deinit();
528# endif /* PJMEDIA_HAS_GSM_CODEC */
529
530# if PJMEDIA_HAS_G711_CODEC
531 pjmedia_codec_g711_deinit();
532# endif /* PJMEDIA_HAS_G711_CODEC */
533
Benny Prijono7ffd7752008-03-17 14:07:53 +0000534# if PJMEDIA_HAS_G722_CODEC
535 pjmedia_codec_g722_deinit();
536# endif /* PJMEDIA_HAS_G722_CODEC */
537
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000538# if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000539 pjmedia_codec_ipp_deinit();
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000540# endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000541
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000542# if PJMEDIA_HAS_PASSTHROUGH_CODECS
543 pjmedia_codec_passthrough_deinit();
544# endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
545
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000546# if PJMEDIA_HAS_G7221_CODEC
547 pjmedia_codec_g7221_deinit();
548# endif /* PJMEDIA_HAS_G7221_CODEC */
549
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000550# if PJMEDIA_HAS_L16_CODEC
551 pjmedia_codec_l16_deinit();
552# endif /* PJMEDIA_HAS_L16_CODEC */
553
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000554 pjmedia_endpt_destroy(pjsua_var.med_endpt);
555 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000556
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000557 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000558 // Not necessary, as pjmedia_snd_deinit() should have been called
559 // in pjmedia_endpt_destroy().
560 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000561 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000562
Benny Prijonode479562007-03-15 10:23:55 +0000563 /* Reset RTP port */
564 next_rtp_port = 0;
565
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000566 return PJ_SUCCESS;
567}
568
Benny Prijono0bc99a92011-03-17 04:34:43 +0000569/*
570 * Create RTP and RTCP socket pair, and possibly resolve their public
571 * address via STUN.
572 */
573static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
574 pjmedia_sock_info *skinfo)
575{
576 enum {
577 RTP_RETRY = 100
578 };
579 int i;
580 pj_sockaddr_in bound_addr;
581 pj_sockaddr_in mapped_addr[2];
582 pj_status_t status = PJ_SUCCESS;
583 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
584 pj_sock_t sock[2];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000585
Benny Prijono0bc99a92011-03-17 04:34:43 +0000586 /* Make sure STUN server resolution has completed */
587 status = resolve_stun_server(PJ_TRUE);
588 if (status != PJ_SUCCESS) {
589 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
590 return status;
591 }
592
593 if (next_rtp_port == 0)
594 next_rtp_port = (pj_uint16_t)cfg->port;
595
596 if (next_rtp_port == 0)
597 next_rtp_port = (pj_uint16_t)40000;
598
599 for (i=0; i<2; ++i)
600 sock[i] = PJ_INVALID_SOCKET;
601
602 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
603 if (cfg->bound_addr.slen) {
604 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
605 if (status != PJ_SUCCESS) {
606 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
607 status);
608 return status;
609 }
610 }
611
612 /* Loop retry to bind RTP and RTCP sockets. */
613 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
614
615 /* Create RTP socket. */
616 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
617 if (status != PJ_SUCCESS) {
618 pjsua_perror(THIS_FILE, "socket() error", status);
619 return status;
620 }
621
622 /* Apply QoS to RTP socket, if specified */
623 status = pj_sock_apply_qos2(sock[0], cfg->qos_type,
624 &cfg->qos_params,
625 2, THIS_FILE, "RTP socket");
626
627 /* Bind RTP socket */
628 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
629 next_rtp_port);
630 if (status != PJ_SUCCESS) {
631 pj_sock_close(sock[0]);
632 sock[0] = PJ_INVALID_SOCKET;
633 continue;
634 }
635
636 /* Create RTCP socket. */
637 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
638 if (status != PJ_SUCCESS) {
639 pjsua_perror(THIS_FILE, "socket() error", status);
640 pj_sock_close(sock[0]);
641 return status;
642 }
643
644 /* Apply QoS to RTCP socket, if specified */
645 status = pj_sock_apply_qos2(sock[1], cfg->qos_type,
646 &cfg->qos_params,
647 2, THIS_FILE, "RTCP socket");
648
649 /* Bind RTCP socket */
650 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
651 (pj_uint16_t)(next_rtp_port+1));
652 if (status != PJ_SUCCESS) {
653 pj_sock_close(sock[0]);
654 sock[0] = PJ_INVALID_SOCKET;
655
656 pj_sock_close(sock[1]);
657 sock[1] = PJ_INVALID_SOCKET;
658 continue;
659 }
660
661 /*
662 * If we're configured to use STUN, then find out the mapped address,
663 * and make sure that the mapped RTCP port is adjacent with the RTP.
664 */
665 if (pjsua_var.stun_srv.addr.sa_family != 0) {
666 char ip_addr[32];
667 pj_str_t stun_srv;
668
669 pj_ansi_strcpy(ip_addr,
670 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
671 stun_srv = pj_str(ip_addr);
672
673 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
674 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
675 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
676 mapped_addr);
677 if (status != PJ_SUCCESS) {
678 pjsua_perror(THIS_FILE, "STUN resolve error", status);
679 goto on_error;
680 }
681
682#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
683 if (pj_ntohs(mapped_addr[1].sin_port) ==
684 pj_ntohs(mapped_addr[0].sin_port)+1)
685 {
686 /* Success! */
687 break;
688 }
689
690 pj_sock_close(sock[0]);
691 sock[0] = PJ_INVALID_SOCKET;
692
693 pj_sock_close(sock[1]);
694 sock[1] = PJ_INVALID_SOCKET;
695#else
696 if (pj_ntohs(mapped_addr[1].sin_port) !=
697 pj_ntohs(mapped_addr[0].sin_port)+1)
698 {
699 PJ_LOG(4,(THIS_FILE,
700 "Note: STUN mapped RTCP port %d is not adjacent"
701 " to RTP port %d",
702 pj_ntohs(mapped_addr[1].sin_port),
703 pj_ntohs(mapped_addr[0].sin_port)));
704 }
705 /* Success! */
706 break;
707#endif
708
709 } else if (cfg->public_addr.slen) {
710
711 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
712 (pj_uint16_t)next_rtp_port);
713 if (status != PJ_SUCCESS)
714 goto on_error;
715
716 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
717 (pj_uint16_t)(next_rtp_port+1));
718 if (status != PJ_SUCCESS)
719 goto on_error;
720
721 break;
722
723 } else {
724
725 if (bound_addr.sin_addr.s_addr == 0) {
726 pj_sockaddr addr;
727
728 /* Get local IP address. */
729 status = pj_gethostip(pj_AF_INET(), &addr);
730 if (status != PJ_SUCCESS)
731 goto on_error;
732
733 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
734 }
735
736 for (i=0; i<2; ++i) {
737 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
738 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
739 }
740
741 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
742 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
743 break;
744 }
745 }
746
747 if (sock[0] == PJ_INVALID_SOCKET) {
748 PJ_LOG(1,(THIS_FILE,
749 "Unable to find appropriate RTP/RTCP ports combination"));
750 goto on_error;
751 }
752
753
754 skinfo->rtp_sock = sock[0];
755 pj_memcpy(&skinfo->rtp_addr_name,
756 &mapped_addr[0], sizeof(pj_sockaddr_in));
757
758 skinfo->rtcp_sock = sock[1];
759 pj_memcpy(&skinfo->rtcp_addr_name,
760 &mapped_addr[1], sizeof(pj_sockaddr_in));
761
762 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
763 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
764 sizeof(addr_buf), 3)));
765 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
766 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
767 sizeof(addr_buf), 3)));
768
769 next_rtp_port += 2;
770 return PJ_SUCCESS;
771
772on_error:
773 for (i=0; i<2; ++i) {
774 if (sock[i] != PJ_INVALID_SOCKET)
775 pj_sock_close(sock[i]);
776 }
777 return status;
778}
779
780/* Create normal UDP media transports */
781static pj_status_t create_udp_media_transport(const pjsua_transport_config *cfg,
782 pjsua_call_media *call_med)
783{
784 pjmedia_sock_info skinfo;
785 pj_status_t status;
786
787 status = create_rtp_rtcp_sock(cfg, &skinfo);
788 if (status != PJ_SUCCESS) {
789 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
790 status);
791 goto on_error;
792 }
793
794 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
795 &skinfo, 0, &call_med->tp);
796 if (status != PJ_SUCCESS) {
797 pjsua_perror(THIS_FILE, "Unable to create media transport",
798 status);
799 goto on_error;
800 }
801
802 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
803 pjsua_var.media_cfg.tx_drop_pct);
804
805 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
806 pjsua_var.media_cfg.rx_drop_pct);
807
808 return PJ_SUCCESS;
809
810on_error:
811 if (call_med->tp)
812 pjmedia_transport_close(call_med->tp);
813
814 return status;
815}
816
817#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +0000818/* Create normal UDP media transports */
819static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000820{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000821 unsigned i;
822 pj_status_t status;
823
Benny Prijono0bc99a92011-03-17 04:34:43 +0000824 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
825 pjsua_call *call = &pjsua_var.calls[i];
826 unsigned strm_idx;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000827
Benny Prijono0bc99a92011-03-17 04:34:43 +0000828 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
829 pjsua_call_media *call_med = &call->media[strm_idx];
830
831 status = create_udp_media_transport(cfg, &call_med->tp);
832 if (status != PJ_SUCCESS)
833 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000834 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000835 }
836
Benny Prijonoc97608e2007-03-23 16:34:20 +0000837 return PJ_SUCCESS;
838
839on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000840 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
841 pjsua_call *call = &pjsua_var.calls[i];
842 unsigned strm_idx;
843
844 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
845 pjsua_call_media *call_med = &call->media[strm_idx];
846
847 if (call_med->tp) {
848 pjmedia_transport_close(call_med->tp);
849 call_med->tp = NULL;
850 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000851 }
852 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000853 return status;
854}
Benny Prijono0bc99a92011-03-17 04:34:43 +0000855#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000856
Benny Prijono096c56c2007-09-15 08:30:16 +0000857/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000858static void on_ice_complete(pjmedia_transport *tp,
859 pj_ice_strans_op op,
860 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000861{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000862 pjsua_call_media *call_med = (pjsua_call_media*)tp->user_data;
Benny Prijono096c56c2007-09-15 08:30:16 +0000863
Benny Prijono0bc99a92011-03-17 04:34:43 +0000864 if (!call_med)
Benny Prijonof76e1392008-06-06 14:51:48 +0000865 return;
866
867 switch (op) {
868 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000869 call_med->tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000870 break;
871 case PJ_ICE_STRANS_OP_NEGOTIATION:
872 if (result != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000873 call_med->state = PJSUA_CALL_MEDIA_ERROR;
874 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijonof76e1392008-06-06 14:51:48 +0000875
Benny Prijono0bc99a92011-03-17 04:34:43 +0000876 if (call_med->call && pjsua_var.ua_cfg.cb.on_call_media_state) {
877 pjsua_var.ua_cfg.cb.on_call_media_state(call_med->call->index);
Benny Prijonof76e1392008-06-06 14:51:48 +0000878 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000879 } else if (call_med->call) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000880 /* Send UPDATE if default transport address is different than
881 * what was advertised (ticket #881)
882 */
883 pjmedia_transport_info tpinfo;
884 pjmedia_ice_transport_info *ii = NULL;
885 unsigned i;
886
887 pjmedia_transport_info_init(&tpinfo);
888 pjmedia_transport_get_info(tp, &tpinfo);
889 for (i=0; i<tpinfo.specific_info_cnt; ++i) {
890 if (tpinfo.spc_info[i].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
891 ii = (pjmedia_ice_transport_info*)
892 tpinfo.spc_info[i].buffer;
893 break;
894 }
895 }
896
897 if (ii && ii->role==PJ_ICE_SESS_ROLE_CONTROLLING &&
898 pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000899 &call_med->rtp_addr))
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000900 {
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000901 pj_bool_t use_update;
902 const pj_str_t STR_UPDATE = { "UPDATE", 6 };
903 pjsip_dialog_cap_status support_update;
904 pjsip_dialog *dlg;
905
Benny Prijono0bc99a92011-03-17 04:34:43 +0000906 dlg = call_med->call->inv->dlg;
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000907 support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW,
908 NULL, &STR_UPDATE);
909 use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED);
910
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000911 PJ_LOG(4,(THIS_FILE,
912 "ICE default transport address has changed for "
Benny Prijono0bc99a92011-03-17 04:34:43 +0000913 "call %d, sending %s", call_med->call->index,
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000914 (use_update ? "UPDATE" : "re-INVITE")));
915
916 if (use_update)
Benny Prijono0bc99a92011-03-17 04:34:43 +0000917 pjsua_call_update(call_med->call->index, 0, NULL);
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000918 else
Benny Prijono0bc99a92011-03-17 04:34:43 +0000919 pjsua_call_reinvite(call_med->call->index, 0, NULL);
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000920 }
Benny Prijonof76e1392008-06-06 14:51:48 +0000921 }
922 break;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000923 case PJ_ICE_STRANS_OP_KEEP_ALIVE:
924 if (result != PJ_SUCCESS) {
925 PJ_PERROR(4,(THIS_FILE, result,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000926 "ICE keep alive failure for transport %d:%d",
927 call_med->call->index, call_med->idx));
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000928 }
929 if (pjsua_var.ua_cfg.cb.on_ice_transport_error) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000930 pjsua_call_id id = call_med->call->index;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000931 (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result,
932 NULL);
933 }
934 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000935 }
936}
937
938
Benny Prijonof76e1392008-06-06 14:51:48 +0000939/* Parse "HOST:PORT" format */
940static pj_status_t parse_host_port(const pj_str_t *host_port,
941 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000942{
Benny Prijonof76e1392008-06-06 14:51:48 +0000943 pj_str_t str_port;
944
945 str_port.ptr = pj_strchr(host_port, ':');
946 if (str_port.ptr != NULL) {
947 int iport;
948
949 host->ptr = host_port->ptr;
950 host->slen = (str_port.ptr - host->ptr);
951 str_port.ptr++;
952 str_port.slen = host_port->slen - host->slen - 1;
953 iport = (int)pj_strtoul(&str_port);
954 if (iport < 1 || iport > 65535)
955 return PJ_EINVAL;
956 *port = (pj_uint16_t)iport;
957 } else {
958 *host = *host_port;
959 *port = 0;
960 }
961
962 return PJ_SUCCESS;
963}
964
965/* Create ICE media transports (when ice is enabled) */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000966static pj_status_t create_ice_media_transport(
967 const pjsua_transport_config *cfg,
968 pjsua_call_media *call_med)
Benny Prijonof76e1392008-06-06 14:51:48 +0000969{
970 char stunip[PJ_INET6_ADDRSTRLEN];
971 pj_ice_strans_cfg ice_cfg;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000972 pjmedia_ice_cb ice_cb;
973 char name[32];
974 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000975 pj_status_t status;
976
Benny Prijonoda9785b2007-04-02 20:43:06 +0000977 /* Make sure STUN server resolution has completed */
Benny Prijonobb995fd2009-08-12 11:03:23 +0000978 status = resolve_stun_server(PJ_TRUE);
Benny Prijonoda9785b2007-04-02 20:43:06 +0000979 if (status != PJ_SUCCESS) {
980 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
981 return status;
982 }
983
Benny Prijonof76e1392008-06-06 14:51:48 +0000984 /* Create ICE stream transport configuration */
985 pj_ice_strans_cfg_default(&ice_cfg);
986 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
987 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
988 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
989
990 ice_cfg.af = pj_AF_INET();
991 ice_cfg.resolver = pjsua_var.resolver;
992
Benny Prijono329d6382009-05-29 13:04:03 +0000993 ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
994
Benny Prijonof76e1392008-06-06 14:51:48 +0000995 /* Configure STUN settings */
996 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
997 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
998 ice_cfg.stun.server = pj_str(stunip);
999 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
1000 }
Benny Prijono329d6382009-05-29 13:04:03 +00001001 if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
1002 ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
Benny Prijonof76e1392008-06-06 14:51:48 +00001003
Benny Prijono4d79b0f2009-10-25 09:02:07 +00001004 /* Copy QoS setting to STUN setting */
1005 ice_cfg.stun.cfg.qos_type = cfg->qos_type;
1006 pj_memcpy(&ice_cfg.stun.cfg.qos_params, &cfg->qos_params,
1007 sizeof(cfg->qos_params));
1008
Benny Prijonof76e1392008-06-06 14:51:48 +00001009 /* Configure TURN settings */
1010 if (pjsua_var.media_cfg.enable_turn) {
1011 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
1012 &ice_cfg.turn.server,
1013 &ice_cfg.turn.port);
1014 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
1015 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
1016 return PJ_EINVAL;
1017 }
1018 if (ice_cfg.turn.port == 0)
1019 ice_cfg.turn.port = 3479;
1020 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
1021 pj_memcpy(&ice_cfg.turn.auth_cred,
1022 &pjsua_var.media_cfg.turn_auth_cred,
1023 sizeof(ice_cfg.turn.auth_cred));
Benny Prijono4d79b0f2009-10-25 09:02:07 +00001024
1025 /* Copy QoS setting to TURN setting */
1026 ice_cfg.turn.cfg.qos_type = cfg->qos_type;
1027 pj_memcpy(&ice_cfg.turn.cfg.qos_params, &cfg->qos_params,
1028 sizeof(cfg->qos_params));
Benny Prijonof76e1392008-06-06 14:51:48 +00001029 }
Benny Prijonob681a2f2007-03-25 18:44:51 +00001030
Benny Prijono0bc99a92011-03-17 04:34:43 +00001031 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
1032 ice_cb.on_ice_complete = &on_ice_complete;
1033 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", call_med->idx);
1034 call_med->tp_ready = PJ_EPENDING;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001035
Benny Prijono0bc99a92011-03-17 04:34:43 +00001036 comp_cnt = 1;
1037 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
1038 ++comp_cnt;
Benny Prijonof76e1392008-06-06 14:51:48 +00001039
Benny Prijonobd6613f2011-04-11 17:27:14 +00001040 status = pjmedia_ice_create3(pjsua_var.med_endpt, name, comp_cnt,
1041 &ice_cfg, &ice_cb, 0, call_med,
1042 &call_med->tp);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001043 if (status != PJ_SUCCESS) {
1044 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
1045 status);
1046 goto on_error;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001047 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001048
Benny Prijono0bc99a92011-03-17 04:34:43 +00001049 /* Wait until transport is initialized, or time out */
1050 PJSUA_UNLOCK();
1051 while (call_med->tp_ready == PJ_EPENDING) {
1052 pjsua_handle_events(100);
1053 }
1054 PJSUA_LOCK();
1055 if (call_med->tp_ready != PJ_SUCCESS) {
1056 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
1057 call_med->tp_ready);
1058 status = call_med->tp_ready;
1059 goto on_error;
1060 }
1061
1062 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
1063 pjsua_var.media_cfg.tx_drop_pct);
1064
1065 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
1066 pjsua_var.media_cfg.rx_drop_pct);
1067
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001068 return PJ_SUCCESS;
1069
1070on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +00001071 if (call_med->tp != NULL) {
1072 pjmedia_transport_close(call_med->tp);
1073 call_med->tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001074 }
1075
Benny Prijonoc97608e2007-03-23 16:34:20 +00001076 return status;
1077}
1078
Benny Prijono0bc99a92011-03-17 04:34:43 +00001079#if DISABLED_FOR_TICKET_1185
1080/* Create ICE media transports (when ice is enabled) */
1081static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
1082{
1083 unsigned i;
1084 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001085
Benny Prijono0bc99a92011-03-17 04:34:43 +00001086 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
1087 pjsua_call *call = &pjsua_var.calls[i];
1088 unsigned strm_idx;
1089
1090 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1091 pjsua_call_media *call_med = &call->media[strm_idx];
1092
1093 status = create_ice_media_transport(cfg, call_med);
1094 if (status != PJ_SUCCESS)
1095 goto on_error;
1096 }
1097 }
1098
1099 return PJ_SUCCESS;
1100
1101on_error:
1102 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
1103 pjsua_call *call = &pjsua_var.calls[i];
1104 unsigned strm_idx;
1105
1106 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1107 pjsua_call_media *call_med = &call->media[strm_idx];
1108
1109 if (call_med->tp) {
1110 pjmedia_transport_close(call_med->tp);
1111 call_med->tp = NULL;
1112 }
1113 }
1114 }
1115 return status;
1116}
1117#endif
1118
1119#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +00001120/*
Benny Prijono0bc99a92011-03-17 04:34:43 +00001121 * Create media transports for all the calls. This function creates
Benny Prijonoc97608e2007-03-23 16:34:20 +00001122 * one UDP media transport for each call.
1123 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001124PJ_DEF(pj_status_t) pjsua_media_transports_create(
1125 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001126{
1127 pjsua_transport_config cfg;
1128 unsigned i;
1129 pj_status_t status;
1130
1131
1132 /* Make sure pjsua_init() has been called */
1133 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
1134
1135 PJSUA_LOCK();
1136
1137 /* Delete existing media transports */
1138 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001139 pjsua_call *call = &pjsua_var.calls[i];
1140 unsigned strm_idx;
1141
1142 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1143 pjsua_call_media *call_med = &call->media[strm_idx];
1144
1145 if (call_med->tp && call_med->tp_auto_del) {
1146 pjmedia_transport_close(call_med->tp);
1147 call_med->tp = NULL;
1148 call_med->tp_orig = NULL;
1149 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001150 }
1151 }
1152
1153 /* Copy config */
1154 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
1155
Benny Prijono40860c32008-09-04 13:55:33 +00001156 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001157 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijono4d79b0f2009-10-25 09:02:07 +00001158 status = create_ice_media_transports(&cfg);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001159 } else {
1160 status = create_udp_media_transports(&cfg);
1161 }
1162
Benny Prijono40860c32008-09-04 13:55:33 +00001163 /* Set media transport auto_delete to True */
1164 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001165 pjsua_call *call = &pjsua_var.calls[i];
1166 unsigned strm_idx;
1167
1168 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1169 pjsua_call_media *call_med = &call->media[strm_idx];
1170
1171 call_med->tp_auto_del = PJ_TRUE;
1172 }
Benny Prijono40860c32008-09-04 13:55:33 +00001173 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001174
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001175 PJSUA_UNLOCK();
1176
1177 return status;
1178}
1179
Benny Prijono40860c32008-09-04 13:55:33 +00001180/*
1181 * Attach application's created media transports.
1182 */
1183PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
1184 unsigned count,
1185 pj_bool_t auto_delete)
1186{
1187 unsigned i;
1188
1189 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
1190
1191 /* Assign the media transports */
1192 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001193 pjsua_call *call = &pjsua_var.calls[i];
1194 unsigned strm_idx;
1195
1196 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1197 pjsua_call_media *call_med = &call->media[strm_idx];
1198
1199 if (call_med->tp && call_med->tp_auto_del) {
1200 pjmedia_transport_close(call_med->tp);
1201 call_med->tp = NULL;
1202 call_med->tp_orig = NULL;
1203 }
Benny Prijono40860c32008-09-04 13:55:33 +00001204 }
1205
Benny Prijono0bc99a92011-03-17 04:34:43 +00001206 PJ_TODO(remove_pjsua_media_transports_attach);
1207
1208 call->media[0].tp = tp[i].transport;
1209 call->media[0].tp_auto_del = auto_delete;
Benny Prijono40860c32008-09-04 13:55:33 +00001210 }
1211
1212 return PJ_SUCCESS;
1213}
Benny Prijono0bc99a92011-03-17 04:34:43 +00001214#endif
Benny Prijono40860c32008-09-04 13:55:33 +00001215
Benny Prijono0bc99a92011-03-17 04:34:43 +00001216/* Go through the list of media in the SDP, find acceptable media, and
1217 * sort them based on the "quality" of the media, and store the indexes
1218 * in the specified array. Media with the best quality will be listed
1219 * first in the array. The quality factors considered currently is
1220 * encryption.
1221 */
1222static void sort_media(const pjmedia_sdp_session *sdp,
1223 const pj_str_t *type,
1224 pjmedia_srtp_use use_srtp,
1225 pj_uint8_t midx[],
1226 unsigned *p_count)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001227{
1228 unsigned i;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001229 unsigned count = 0;
1230 int score[PJSUA_MAX_CALL_MEDIA];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001231
Benny Prijono0bc99a92011-03-17 04:34:43 +00001232 pj_assert(*p_count >= PJSUA_MAX_CALL_MEDIA);
1233
1234 *p_count = 0;
Benny Prijono09c0d672011-04-11 05:03:24 +00001235 for (i=0; i<PJSUA_MAX_CALL_MEDIA; ++i)
1236 score[i] = 1;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001237
1238 /* Score each media */
1239 for (i=0; i<sdp->media_count && count<PJSUA_MAX_CALL_MEDIA; ++i) {
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001240 const pjmedia_sdp_media *m = sdp->media[i];
Nanang Izzuddina6414292011-04-08 04:26:18 +00001241 const pjmedia_sdp_conn *c;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001242
Benny Prijono0bc99a92011-03-17 04:34:43 +00001243 /* Skip different media */
1244 if (pj_stricmp(&m->desc.media, type) != 0) {
1245 score[count++] = -22000;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001246 continue;
1247 }
1248
Nanang Izzuddina6414292011-04-08 04:26:18 +00001249 c = m->conn? m->conn : sdp->conn;
1250
Benny Prijono0bc99a92011-03-17 04:34:43 +00001251 /* Supported transports */
1252 if (pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0) {
1253 switch (use_srtp) {
1254 case PJMEDIA_SRTP_MANDATORY:
1255 case PJMEDIA_SRTP_OPTIONAL:
1256 ++score[i];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001257 break;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001258 case PJMEDIA_SRTP_DISABLED:
1259 --score[i];
1260 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001261 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001262 } else if (pj_stricmp2(&m->desc.transport, "RTP/AVP")==0) {
1263 switch (use_srtp) {
1264 case PJMEDIA_SRTP_MANDATORY:
1265 --score[i];
1266 break;
1267 case PJMEDIA_SRTP_OPTIONAL:
1268 /* No change in score */
1269 break;
1270 case PJMEDIA_SRTP_DISABLED:
1271 ++score[i];
1272 break;
1273 }
1274 } else {
1275 score[i] -= 10;
1276 }
1277
1278 /* Is media disabled? */
1279 if (m->desc.port == 0)
1280 score[i] -= 10;
1281
1282 /* Is media inactive? */
Nanang Izzuddina6414292011-04-08 04:26:18 +00001283 if (pjmedia_sdp_media_find_attr2(m, "inactive", NULL) ||
1284 pj_strcmp2(&c->addr, "0.0.0.0") == 0)
1285 {
1286 //score[i] -= 10;
1287 score[i] -= 1;
1288 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001289
1290 ++count;
1291 }
1292
1293 /* Created sorted list based on quality */
1294 for (i=0; i<count; ++i) {
1295 unsigned j;
1296 int best = 0;
1297
1298 for (j=1; j<count; ++j) {
1299 if (score[j] > score[best])
1300 best = j;
1301 }
1302 /* Don't put media with negative score, that media is unacceptable
1303 * for us.
1304 */
1305 if (score[best] >= 0) {
1306 midx[*p_count] = (pj_uint8_t)best;
1307 (*p_count)++;
1308 }
1309
1310 score[best] = -22000;
1311
1312 }
1313}
1314
1315/* Initialize the media line */
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001316pj_status_t pjsua_call_media_init(pjsua_call_media *call_med,
1317 pjmedia_type type,
1318 const pjsua_transport_config *tcfg,
1319 int security_level,
1320 int *sip_err_code)
Benny Prijono0bc99a92011-03-17 04:34:43 +00001321{
1322 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
1323 pj_status_t status;
1324
1325 /*
1326 * Note: this function may be called when the media already exists
1327 * (e.g. in reinvites, updates, etc.)
1328 */
1329 call_med->type = type;
1330
1331 /* Create the media transport for initial call. This is blocking for now */
1332 if (call_med->tp == NULL) {
1333 if (pjsua_var.media_cfg.enable_ice) {
1334 status = create_ice_media_transport(tcfg, call_med);
1335 } else {
1336 status = create_udp_media_transport(tcfg, call_med);
1337 }
1338
1339 if (status != PJ_SUCCESS) {
1340 PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport"));
1341 return status;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001342 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001343
1344 call_med->tp_st = PJSUA_MED_TP_IDLE;
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001345
1346 /* While in initial call, set default video devices */
1347 if (type == PJMEDIA_TYPE_VIDEO) {
1348 call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev;
1349 call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev;
1350 if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) {
1351 pjmedia_vid_dev_info info;
1352 pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info);
1353 call_med->strm.v.rdr_dev = info.id;
1354 }
1355 if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
1356 pjmedia_vid_dev_info info;
1357 pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info);
1358 call_med->strm.v.cap_dev = info.id;
1359 }
1360 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001361 } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) {
1362 /* Media is being reenabled. */
1363 call_med->tp_st = PJSUA_MED_TP_INIT;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001364 }
1365
Benny Prijono0bc99a92011-03-17 04:34:43 +00001366#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1367 /* This function may be called when SRTP transport already exists
1368 * (e.g: in re-invite, update), don't need to destroy/re-create.
1369 */
1370 if (!call_med->tp_orig || call_med->tp == call_med->tp_orig) {
1371 pjmedia_srtp_setting srtp_opt;
1372 pjmedia_transport *srtp = NULL;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001373
Benny Prijono0bc99a92011-03-17 04:34:43 +00001374 /* Check if SRTP requires secure signaling */
1375 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
1376 if (security_level < acc->cfg.srtp_secure_signaling) {
1377 if (sip_err_code)
1378 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1379 status = PJSIP_ESESSIONINSECURE;
1380 goto on_error;
1381 }
1382 }
1383
1384 /* Always create SRTP adapter */
1385 pjmedia_srtp_setting_default(&srtp_opt);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001386 srtp_opt.close_member_tp = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001387 /* If media session has been ever established, let's use remote's
1388 * preference in SRTP usage policy, especially when it is stricter.
1389 */
1390 if (call_med->rem_srtp_use > acc->cfg.use_srtp)
1391 srtp_opt.use = call_med->rem_srtp_use;
1392 else
1393 srtp_opt.use = acc->cfg.use_srtp;
1394
1395 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
1396 call_med->tp,
1397 &srtp_opt, &srtp);
1398 if (status != PJ_SUCCESS) {
1399 if (sip_err_code)
1400 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
1401 goto on_error;
1402 }
1403
1404 /* Set SRTP as current media transport */
1405 call_med->tp_orig = call_med->tp;
1406 call_med->tp = srtp;
1407 }
1408#else
1409 call->tp_orig = call->tp;
1410 PJ_UNUSED_ARG(security_level);
1411#endif
1412
1413 return PJ_SUCCESS;
1414
1415on_error:
1416 if (call_med->tp) {
1417 pjmedia_transport_close(call_med->tp);
1418 call_med->tp = NULL;
1419 }
1420 return status;
1421}
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001422
Benny Prijonoc97608e2007-03-23 16:34:20 +00001423pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
Benny Prijonod8179652008-01-23 20:39:07 +00001424 pjsip_role_e role,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001425 int security_level,
Benny Prijono224b4e22008-06-19 14:10:28 +00001426 pj_pool_t *tmp_pool,
1427 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001428 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001429{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001430 const pj_str_t STR_AUDIO = { "audio", 5 };
1431 const pj_str_t STR_VIDEO = { "video", 5 };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001432 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijonod8179652008-01-23 20:39:07 +00001433 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001434 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1435 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
1436 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
1437 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
1438 pjmedia_type media_types[PJSUA_MAX_CALL_MEDIA];
1439 unsigned mi;
1440 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001441
Benny Prijonod8179652008-01-23 20:39:07 +00001442 PJ_UNUSED_ARG(role);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001443
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001444 /*
1445 * Note: this function may be called when the media already exists
1446 * (e.g. in reinvites, updates, etc).
1447 */
1448
Benny Prijono0bc99a92011-03-17 04:34:43 +00001449 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
1450 return PJ_EBUSY;
1451
1452#if DISABLED_FOR_TICKET_1185
Benny Prijonod8179652008-01-23 20:39:07 +00001453 /* Return error if media transport has not been created yet
1454 * (e.g. application is starting)
1455 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001456 for (i=0; i<call->med_cnt; ++i) {
1457 if (call->media[i].tp == NULL) {
1458 return PJ_EBUSY;
Benny Prijonod8179652008-01-23 20:39:07 +00001459 }
Benny Prijono53a7c702008-04-14 02:57:29 +00001460 }
Benny Prijonod8179652008-01-23 20:39:07 +00001461#endif
1462
Benny Prijono0bc99a92011-03-17 04:34:43 +00001463 if (rem_sdp) {
1464 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
1465 maudidx, &maudcnt);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001466 if (maudcnt > acc->cfg.max_audio_cnt)
1467 maudcnt = acc->cfg.max_audio_cnt;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001468
Benny Prijono0bc99a92011-03-17 04:34:43 +00001469 if (maudcnt==0) {
1470 /* Expecting audio in the offer */
1471 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1472 pjsua_media_channel_deinit(call_id);
1473 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
1474 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001475
Benny Prijono0bc99a92011-03-17 04:34:43 +00001476 sort_media(rem_sdp, &STR_VIDEO, acc->cfg.use_srtp,
1477 mvididx, &mvidcnt);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001478 if (mvidcnt > acc->cfg.max_video_cnt)
1479 mvidcnt = acc->cfg.max_video_cnt;
1480
1481 /* Update media count only when remote add any media, this media count
1482 * must never decrease.
1483 */
1484 if (call->med_cnt < rem_sdp->media_count)
1485 call->med_cnt = PJ_MIN(rem_sdp->media_count, PJSUA_MAX_CALL_MEDIA);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001486
Benny Prijono0bc99a92011-03-17 04:34:43 +00001487 } else {
1488 maudcnt = acc->cfg.max_audio_cnt;
1489 for (mi=0; mi<maudcnt; ++mi) {
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001490 maudidx[mi] = (pj_uint8_t)mi;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001491 media_types[mi] = PJMEDIA_TYPE_AUDIO;
1492 }
1493 mvidcnt = acc->cfg.max_video_cnt;
1494 for (mi=0; mi<mvidcnt; ++mi) {
1495 media_types[maudcnt + mi] = PJMEDIA_TYPE_VIDEO;
1496 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001497
1498 call->med_cnt = maudcnt + mvidcnt;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001499 }
1500
Benny Prijono0bc99a92011-03-17 04:34:43 +00001501 if (call->med_cnt == 0) {
1502 /* Expecting at least one media */
Benny Prijonoab8dba92008-06-27 21:59:15 +00001503 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001504 pjsua_media_channel_deinit(call_id);
Benny Prijonoab8dba92008-06-27 21:59:15 +00001505 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001506 }
1507
Benny Prijono0bc99a92011-03-17 04:34:43 +00001508 /* Initialize each media line */
1509 for (mi=0; mi < call->med_cnt; ++mi) {
1510 pjsua_call_media *call_med = &call->media[mi];
1511 pj_bool_t enabled = PJ_FALSE;
1512 pjmedia_type media_type = PJMEDIA_TYPE_NONE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001513
Benny Prijono0bc99a92011-03-17 04:34:43 +00001514 if (rem_sdp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001515 if (mi >= rem_sdp->media_count) {
1516 /* Media has been removed in remote re-offer */
1517 media_type = call_med->type;
1518 } else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_AUDIO)) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001519 media_type = PJMEDIA_TYPE_AUDIO;
1520 if (pj_memchr(maudidx, mi, maudcnt * sizeof(maudidx[0]))) {
1521 enabled = PJ_TRUE;
1522 }
1523 }
1524 else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_VIDEO)) {
1525 media_type = PJMEDIA_TYPE_VIDEO;
1526 if (pj_memchr(mvididx, mi, mvidcnt * sizeof(mvididx[0]))) {
1527 enabled = PJ_TRUE;
1528 }
1529 }
1530
1531 } else {
1532 enabled = PJ_TRUE;
1533 media_type = media_types[mi];
1534 }
1535
1536 if (enabled) {
1537 status = pjsua_call_media_init(call_med, media_type,
1538 &acc->cfg.rtp_cfg,
1539 security_level, sip_err_code);
1540 if (status != PJ_SUCCESS) {
1541 pjsua_media_channel_deinit(call_id);
1542 return status;
1543 }
1544 } else {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001545 /* By convention, the media is disabled if transport is NULL
1546 * or transport state is PJSUA_MED_TP_DISABLED.
1547 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001548 if (call_med->tp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001549 // Don't close transport here, as SDP negotiation has not been
1550 // done and stream may be still active.
1551 //pjmedia_transport_close(call_med->tp);
1552 //call_med->tp = NULL;
1553 pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT ||
1554 call_med->tp_st == PJSUA_MED_TP_RUNNING);
1555 call_med->tp_st = PJSUA_MED_TP_DISABLED;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001556 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001557
1558 /* Put media type just for info */
1559 call_med->type = media_type;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001560 }
Benny Prijono224b4e22008-06-19 14:10:28 +00001561 }
1562
Benny Prijono0bc99a92011-03-17 04:34:43 +00001563 call->audio_idx = maudidx[0];
1564
1565 PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d",
1566 call->audio_idx, call->index));
1567
1568 /* Tell the media transport of a new offer/answer session */
1569 for (mi=0; mi < call->med_cnt; ++mi) {
1570 pjsua_call_media *call_med = &call->media[mi];
1571
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001572 /* Note: tp may be NULL if this media line is disabled */
1573 if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001574 status = pjmedia_transport_media_create(call_med->tp,
1575 tmp_pool, 0,
1576 rem_sdp, mi);
1577 if (status != PJ_SUCCESS) {
1578 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1579 pjsua_media_channel_deinit(call_id);
1580 return status;
1581 }
1582
1583 call_med->tp_st = PJSUA_MED_TP_INIT;
1584 }
1585 }
1586
Benny Prijonoc97608e2007-03-23 16:34:20 +00001587 return PJ_SUCCESS;
1588}
1589
1590pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1591 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001592 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001593 pjmedia_sdp_session **p_sdp,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001594 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001595{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001596 enum { MAX_MEDIA = PJSUA_MAX_CALL_MEDIA };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001597 pjmedia_sdp_session *sdp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001598 pj_sockaddr origin;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001599 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001600 pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001601 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001602 pj_status_t status;
1603
Benny Prijono0bc99a92011-03-17 04:34:43 +00001604 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
Benny Prijono55e82352007-05-10 20:49:08 +00001605 return PJ_EBUSY;
Benny Prijono55e82352007-05-10 20:49:08 +00001606
Benny Prijono0bc99a92011-03-17 04:34:43 +00001607 if (rem_sdp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001608 /* If this is a re-offer, let's re-initialize media as remote may
1609 * add or remove media
1610 */
1611 if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) {
1612 status = pjsua_media_channel_init(call_id, PJSIP_ROLE_UAS,
1613 call->secure_level, pool,
1614 rem_sdp, sip_err_code);
1615 if (status != PJ_SUCCESS)
1616 return status;
1617 }
1618
1619#if 0
1620 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001621 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1622 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001623
Benny Prijono0bc99a92011-03-17 04:34:43 +00001624 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
1625 maudidx, &maudcnt);
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001626
Benny Prijono0bc99a92011-03-17 04:34:43 +00001627 if (maudcnt==0) {
1628 /* Expecting audio in the offer */
1629 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1630 pjsua_media_channel_deinit(call_id);
1631 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijono0324ba52010-12-02 10:41:46 +00001632 }
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001633
Benny Prijono0bc99a92011-03-17 04:34:43 +00001634 call->audio_idx = maudidx[0];
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001635#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00001636 } else {
1637 /* Audio is first in our offer, by convention */
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001638 // The audio_idx should not be changed here, as this function may be
1639 // called in generating re-offer and the current active audio index
1640 // can be anywhere.
1641 //call->audio_idx = 0;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001642 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001643
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001644#if 0
1645 // Since r3512, old-style hold should have got transport, created by
1646 // pjsua_media_channel_init() in initial offer/answer or remote reoffer.
Benny Prijono224b4e22008-06-19 14:10:28 +00001647 /* Create media if it's not created. This could happen when call is
Benny Prijono0bc99a92011-03-17 04:34:43 +00001648 * currently on-hold (with the old style hold)
Benny Prijono224b4e22008-06-19 14:10:28 +00001649 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001650 if (call->media[call->audio_idx].tp == NULL) {
Benny Prijono224b4e22008-06-19 14:10:28 +00001651 pjsip_role_e role;
1652 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1653 status = pjsua_media_channel_init(call_id, role, call->secure_level,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001654 pool, rem_sdp, sip_err_code);
Benny Prijono224b4e22008-06-19 14:10:28 +00001655 if (status != PJ_SUCCESS)
1656 return status;
1657 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001658#endif
Benny Prijono224b4e22008-06-19 14:10:28 +00001659
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001660 /* Get SDP negotiator state */
1661 if (call->inv && call->inv->neg)
1662 sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg);
1663
Benny Prijono0bc99a92011-03-17 04:34:43 +00001664 /* Get one address to use in the origin field */
1665 pj_bzero(&origin, sizeof(origin));
1666 for (mi=0; mi<call->med_cnt; ++mi) {
1667 pjmedia_transport_info tpinfo;
Benny Prijono617c5bc2007-04-02 19:51:21 +00001668
Benny Prijono0bc99a92011-03-17 04:34:43 +00001669 if (call->media[mi].tp == NULL)
1670 continue;
1671
1672 pjmedia_transport_info_init(&tpinfo);
1673 pjmedia_transport_get_info(call->media[mi].tp, &tpinfo);
1674 pj_sockaddr_cp(&origin, &tpinfo.sock_info.rtp_addr_name);
1675 break;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001676 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001677
Benny Prijono0bc99a92011-03-17 04:34:43 +00001678 /* Create the base (blank) SDP */
1679 status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, pool, NULL,
1680 &origin, &sdp);
1681 if (status != PJ_SUCCESS)
1682 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001683
Benny Prijono0bc99a92011-03-17 04:34:43 +00001684 /* Process each media line */
1685 for (mi=0; mi<call->med_cnt; ++mi) {
1686 pjsua_call_media *call_med = &call->media[mi];
1687 pjmedia_sdp_media *m = NULL;
1688 pjmedia_transport_info tpinfo;
1689
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001690 if (rem_sdp && mi >= rem_sdp->media_count) {
1691 /* Remote might have removed some media lines. */
1692 break;
1693 }
1694
1695 if (call_med->tp == NULL || call_med->tp_st == PJSUA_MED_TP_DISABLED)
1696 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001697 /*
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001698 * This media is disabled. Just create a valid SDP with zero
Benny Prijono0bc99a92011-03-17 04:34:43 +00001699 * port.
1700 */
1701 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1702 m->desc.transport = pj_str("RTP/AVP");
1703 m->desc.fmt_count = 1;
1704 m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
1705 m->conn->net_type = pj_str("IN");
1706 m->conn->addr_type = pj_str("IP4");
1707 m->conn->addr = pj_str("127.0.0.1");
1708
1709 switch (call_med->type) {
1710 case PJMEDIA_TYPE_AUDIO:
1711 m->desc.media = pj_str("audio");
1712 m->desc.fmt[0] = pj_str("0");
1713 break;
1714 case PJMEDIA_TYPE_VIDEO:
1715 m->desc.media = pj_str("video");
1716 m->desc.fmt[0] = pj_str("31");
1717 break;
1718 default:
1719 if (rem_sdp && mi < rem_sdp->media_count) {
1720 pj_strdup(pool, &m->desc.media,
1721 &rem_sdp->media[mi]->desc.media);
1722 pj_strdup(pool, &m->desc.fmt[0],
1723 &rem_sdp->media[mi]->desc.fmt[0]);
1724 } else {
1725 pj_assert(!"Invalid call_med media type");
1726 return PJ_EBUG;
1727 }
1728 }
1729
1730 sdp->media[sdp->media_count++] = m;
1731 continue;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001732 }
1733
Benny Prijono0bc99a92011-03-17 04:34:43 +00001734 /* Get transport address info */
1735 pjmedia_transport_info_init(&tpinfo);
1736 pjmedia_transport_get_info(call_med->tp, &tpinfo);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001737
Benny Prijono0bc99a92011-03-17 04:34:43 +00001738 /* Ask pjmedia endpoint to create SDP media line */
1739 switch (call_med->type) {
1740 case PJMEDIA_TYPE_AUDIO:
1741 status = pjmedia_endpt_create_audio_sdp(pjsua_var.med_endpt, pool,
1742 &tpinfo.sock_info, 0, &m);
1743 break;
1744 case PJMEDIA_TYPE_VIDEO:
1745 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
1746 &tpinfo.sock_info, 0, &m);
1747 break;
1748 default:
1749 pj_assert(!"Invalid call_med media type");
1750 return PJ_EBUG;
1751 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001752
Benny Prijono0bc99a92011-03-17 04:34:43 +00001753 if (status != PJ_SUCCESS)
1754 return status;
1755
1756 sdp->media[sdp->media_count++] = m;
1757
1758 /* Give to transport */
1759 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
1760 sdp, rem_sdp, mi);
1761 if (status != PJ_SUCCESS) {
1762 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1763 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001764 }
Nanang Izzuddin91ba2a22011-04-11 19:59:09 +00001765
1766 /* Copy c= line of the first media to session level,
1767 * if there's none.
1768 */
1769 if (sdp->conn == NULL) {
1770 sdp->conn = pjmedia_sdp_conn_clone(pool, m->conn);
1771 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001772 }
1773
Benny Prijono6ba8c542007-10-16 01:34:14 +00001774 /* Add NAT info in the SDP */
1775 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1776 pjmedia_sdp_attr *a;
1777 pj_str_t value;
1778 char nat_info[80];
1779
1780 value.ptr = nat_info;
1781 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1782 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1783 "%d", pjsua_var.nat_type);
1784 } else {
1785 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1786 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1787 "%d %s",
1788 pjsua_var.nat_type,
1789 type_name);
1790 }
1791
1792 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1793
1794 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1795
1796 }
1797
Benny Prijonoc97608e2007-03-23 16:34:20 +00001798
Benny Prijono0bc99a92011-03-17 04:34:43 +00001799#if DISABLED_FOR_TICKET_1185 && defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001800 /* Check if SRTP is in optional mode and configured to use duplicated
1801 * media, i.e: secured and unsecured version, in the SDP offer.
1802 */
1803 if (!rem_sdp &&
1804 pjsua_var.acc[call->acc_id].cfg.use_srtp == PJMEDIA_SRTP_OPTIONAL &&
1805 pjsua_var.acc[call->acc_id].cfg.srtp_optional_dup_offer)
1806 {
1807 unsigned i;
1808
1809 for (i = 0; i < sdp->media_count; ++i) {
1810 pjmedia_sdp_media *m = sdp->media[i];
1811
Benny Prijono0bc99a92011-03-17 04:34:43 +00001812 /* Check if this media is unsecured but has SDP "crypto"
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001813 * attribute.
1814 */
1815 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") == 0 &&
1816 pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL)
1817 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001818 if (i == (unsigned)call->audio_idx &&
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001819 sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE)
1820 {
1821 /* This is a session update, and peer has chosen the
1822 * unsecured version, so let's make this unsecured too.
1823 */
1824 pjmedia_sdp_media_remove_all_attr(m, "crypto");
1825 } else {
1826 /* This is new offer, duplicate media so we'll have
1827 * secured (with "RTP/SAVP" transport) and and unsecured
1828 * versions.
1829 */
1830 pjmedia_sdp_media *new_m;
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001831
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001832 /* Duplicate this media and apply secured transport */
1833 new_m = pjmedia_sdp_media_clone(pool, m);
1834 pj_strdup2(pool, &new_m->desc.transport, "RTP/SAVP");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001835
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001836 /* Remove the "crypto" attribute in the unsecured media */
1837 pjmedia_sdp_media_remove_all_attr(m, "crypto");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001838
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001839 /* Insert the new media before the unsecured media */
1840 if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001841 pj_array_insert(sdp->media, sizeof(new_m),
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001842 sdp->media_count, i, &new_m);
1843 ++sdp->media_count;
1844 ++i;
1845 }
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001846 }
1847 }
1848 }
1849 }
1850#endif
1851
Benny Prijonoc97608e2007-03-23 16:34:20 +00001852 *p_sdp = sdp;
1853 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001854}
1855
1856
Nanang Izzuddin62053a62011-07-12 11:08:32 +00001857void stop_video_stream(pjsua_call_media *call_med);
1858
Benny Prijonoc97608e2007-03-23 16:34:20 +00001859static void stop_media_session(pjsua_call_id call_id)
1860{
1861 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001862 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001863
Benny Prijono0bc99a92011-03-17 04:34:43 +00001864 for (mi=0; mi<call->med_cnt; ++mi) {
1865 pjsua_call_media *call_med = &call->media[mi];
1866
1867 if (call_med->type == PJMEDIA_TYPE_AUDIO) {
1868 pjmedia_stream *strm = call_med->strm.a.stream;
1869 pjmedia_rtcp_stat stat;
1870
1871 if (strm) {
1872 if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) {
1873 if (pjsua_var.mconf) {
1874 pjsua_conf_remove_port(call_med->strm.a.conf_slot);
1875 }
1876 call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
1877 }
1878
1879 if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
1880 (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS))
1881 {
1882 /* Save RTP timestamp & sequence, so when media session is
1883 * restarted, those values will be restored as the initial
1884 * RTP timestamp & sequence of the new media session. So in
1885 * the same call session, RTP timestamp and sequence are
1886 * guaranteed to be contigue.
1887 */
1888 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
1889 call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
1890 call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
1891 }
1892
1893 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1894 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, strm, mi);
1895 }
1896
1897 pjmedia_stream_destroy(strm);
1898 call_med->strm.a.stream = NULL;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001899 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00001900 }
1901
1902#if PJMEDIA_HAS_VIDEO
1903 else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
Nanang Izzuddin62053a62011-07-12 11:08:32 +00001904 stop_video_stream(call_med);
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001905 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00001906#endif
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001907
1908 PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed",
1909 call_id, mi));
Benny Prijono0bc99a92011-03-17 04:34:43 +00001910 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001911 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001912}
1913
1914pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1915{
1916 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001917 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001918
1919 stop_media_session(call_id);
1920
Benny Prijono0bc99a92011-03-17 04:34:43 +00001921 for (mi=0; mi<call->med_cnt; ++mi) {
1922 pjsua_call_media *call_med = &call->media[mi];
Benny Prijonoc97608e2007-03-23 16:34:20 +00001923
Benny Prijono0bc99a92011-03-17 04:34:43 +00001924 if (call_med->tp_st != PJSUA_MED_TP_IDLE) {
1925 pjmedia_transport_media_stop(call_med->tp);
1926 call_med->tp_st = PJSUA_MED_TP_IDLE;
1927 }
1928
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001929 //if (call_med->tp_orig && call_med->tp &&
1930 // call_med->tp != call_med->tp_orig)
1931 //{
1932 // pjmedia_transport_close(call_med->tp);
1933 // call_med->tp = call_med->tp_orig;
1934 //}
1935 if (call_med->tp) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001936 pjmedia_transport_close(call_med->tp);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001937 call_med->tp = call_med->tp_orig = NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001938 }
Benny Prijonod8179652008-01-23 20:39:07 +00001939 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00001940
1941 check_snd_dev_idle();
1942
Benny Prijonoc97608e2007-03-23 16:34:20 +00001943 return PJ_SUCCESS;
1944}
1945
1946
1947/*
1948 * DTMF callback from the stream.
1949 */
1950static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1951 int digit)
1952{
1953 PJ_UNUSED_ARG(strm);
1954
Benny Prijono0c068262008-02-14 14:38:52 +00001955 /* For discussions about call mutex protection related to this
1956 * callback, please see ticket #460:
1957 * http://trac.pjsip.org/repos/ticket/460#comment:4
1958 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001959 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1960 pjsua_call_id call_id;
1961
Benny Prijonod8179652008-01-23 20:39:07 +00001962 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001963 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1964 }
1965}
1966
1967
Benny Prijono0bc99a92011-03-17 04:34:43 +00001968static pj_status_t audio_channel_update(pjsua_call_media *call_med,
1969 pj_pool_t *tmp_pool,
1970 const pjmedia_sdp_session *local_sdp,
1971 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001972{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001973 pjsua_call *call = call_med->call;
1974 pjmedia_stream_info the_si, *si = &the_si;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001975 pjmedia_port *media_port;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001976 unsigned strm_idx = call_med->idx;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001977 pj_status_t status;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001978
1979 status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
1980 local_sdp, remote_sdp, strm_idx);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001981 if (status != PJ_SUCCESS)
1982 return status;
1983
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001984 /* Check if no media is active */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001985 if (si->dir == PJMEDIA_DIR_NONE) {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001986 /* Call media state */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001987 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001988
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001989 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001990 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001991
Benny Prijonoc97608e2007-03-23 16:34:20 +00001992 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001993 pjmedia_transport_info tp_info;
1994
Benny Prijono224b4e22008-06-19 14:10:28 +00001995 /* Start/restart media transport */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001996 status = pjmedia_transport_media_start(call_med->tp,
1997 tmp_pool, local_sdp,
1998 remote_sdp, strm_idx);
Benny Prijonod8179652008-01-23 20:39:07 +00001999 if (status != PJ_SUCCESS)
2000 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002001
Benny Prijono0bc99a92011-03-17 04:34:43 +00002002 call_med->tp_st = PJSUA_MED_TP_RUNNING;
Benny Prijono224b4e22008-06-19 14:10:28 +00002003
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002004 /* Get remote SRTP usage policy */
2005 pjmedia_transport_info_init(&tp_info);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002006 pjmedia_transport_get_info(call_med->tp, &tp_info);
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002007 if (tp_info.specific_info_cnt > 0) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +00002008 unsigned i;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002009 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
2010 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
2011 {
2012 pjmedia_srtp_info *srtp_info =
2013 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
2014
Benny Prijono0bc99a92011-03-17 04:34:43 +00002015 call_med->rem_srtp_use = srtp_info->peer_use;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002016 break;
2017 }
2018 }
2019 }
2020
Benny Prijonoc97608e2007-03-23 16:34:20 +00002021 /* Override ptime, if this option is specified. */
2022 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00002023 si->param->setting.frm_per_pkt = (pj_uint8_t)
2024 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
2025 if (si->param->setting.frm_per_pkt == 0)
2026 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002027 }
2028
2029 /* Disable VAD, if this option is specified. */
2030 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00002031 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002032 }
2033
2034
2035 /* Optionally, application may modify other stream settings here
2036 * (such as jitter buffer parameters, codec ptime, etc.)
2037 */
Benny Prijono91e567e2007-12-28 08:51:58 +00002038 si->jb_init = pjsua_var.media_cfg.jb_init;
2039 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
2040 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
2041 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002042
Benny Prijono8147f402007-11-21 14:50:07 +00002043 /* Set SSRC */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002044 si->ssrc = call_med->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00002045
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002046 /* Set RTP timestamp & sequence, normally these value are intialized
2047 * automatically when stream session created, but for some cases (e.g:
2048 * call reinvite, call update) timestamp and sequence need to be kept
2049 * contigue.
2050 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002051 si->rtp_ts = call_med->rtp_tx_ts;
2052 si->rtp_seq = call_med->rtp_tx_seq;
2053 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002054
Nanang Izzuddin5e39a2b2010-09-20 06:13:02 +00002055#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
2056 /* Enable/disable stream keep-alive and NAT hole punch. */
2057 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
2058#endif
2059
Benny Prijonoc97608e2007-03-23 16:34:20 +00002060 /* Create session based on session info. */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002061 status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,
2062 call_med->tp, NULL,
2063 &call_med->strm.a.stream);
2064 if (status != PJ_SUCCESS) {
2065 return status;
2066 }
2067
2068 /* Start stream */
2069 status = pjmedia_stream_start(call_med->strm.a.stream);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002070 if (status != PJ_SUCCESS) {
2071 return status;
2072 }
2073
2074 /* If DTMF callback is installed by application, install our
2075 * callback to the session.
2076 */
2077 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002078 pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
2079 &dtmf_callback,
2080 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00002081 }
2082
2083 /* Get the port interface of the first stream in the session.
2084 * We need the port interface to add to the conference bridge.
2085 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002086 pjmedia_stream_get_port(call_med->strm.a.stream, &media_port);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002087
Benny Prijonofc13bf62008-02-20 08:56:15 +00002088 /* Notify application about stream creation.
2089 * Note: application may modify media_port to point to different
2090 * media port
2091 */
2092 if (pjsua_var.ua_cfg.cb.on_stream_created) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002093 pjsua_var.ua_cfg.cb.on_stream_created(call->index,
2094 call_med->strm.a.stream,
2095 strm_idx, &media_port);
Benny Prijonofc13bf62008-02-20 08:56:15 +00002096 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002097
2098 /*
2099 * Add the call to conference bridge.
2100 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002101 {
2102 char tmp[PJSIP_MAX_URL_SIZE];
2103 pj_str_t port_name;
2104
2105 port_name.ptr = tmp;
2106 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
2107 call->inv->dlg->remote.info->uri,
2108 tmp, sizeof(tmp));
2109 if (port_name.slen < 1) {
2110 port_name = pj_str("call");
2111 }
Benny Prijono40d62b62009-08-12 17:53:47 +00002112 status = pjmedia_conf_add_port( pjsua_var.mconf,
2113 call->inv->pool_prov,
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002114 media_port,
2115 &port_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +00002116 (unsigned*)
2117 &call_med->strm.a.conf_slot);
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002118 if (status != PJ_SUCCESS) {
2119 return status;
2120 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002121 }
2122
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002123 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002124 call_med->dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002125
2126 /* Call media state */
2127 if (call->local_hold)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002128 call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
2129 else if (call_med->dir == PJMEDIA_DIR_DECODING)
2130 call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002131 else
Benny Prijono0bc99a92011-03-17 04:34:43 +00002132 call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002133 }
2134
2135 /* Print info. */
2136 {
2137 char info[80];
2138 int info_len = 0;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002139 int len;
2140 const char *dir;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002141
Benny Prijono0bc99a92011-03-17 04:34:43 +00002142 switch (si->dir) {
2143 case PJMEDIA_DIR_NONE:
2144 dir = "inactive";
2145 break;
2146 case PJMEDIA_DIR_ENCODING:
2147 dir = "sendonly";
2148 break;
2149 case PJMEDIA_DIR_DECODING:
2150 dir = "recvonly";
2151 break;
2152 case PJMEDIA_DIR_ENCODING_DECODING:
2153 dir = "sendrecv";
2154 break;
2155 default:
2156 dir = "unknown";
2157 break;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002158 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00002159 len = pj_ansi_sprintf( info+info_len,
2160 ", stream #%d: %.*s (%s)", strm_idx,
2161 (int)si->fmt.encoding_name.slen,
2162 si->fmt.encoding_name.ptr,
2163 dir);
2164 if (len > 0)
2165 info_len += len;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002166 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
2167 }
2168
2169 return PJ_SUCCESS;
2170}
2171
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002172
Nanang Izzuddin62053a62011-07-12 11:08:32 +00002173pj_status_t video_channel_update(pjsua_call_media *call_med,
2174 pj_pool_t *tmp_pool,
2175 const pjmedia_sdp_session *local_sdp,
2176 const pjmedia_sdp_session *remote_sdp);
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002177
Benny Prijono0bc99a92011-03-17 04:34:43 +00002178pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
2179 const pjmedia_sdp_session *local_sdp,
2180 const pjmedia_sdp_session *remote_sdp)
2181{
2182 pjsua_call *call = &pjsua_var.calls[call_id];
2183 pj_pool_t *tmp_pool = call->inv->pool_prov;
2184 unsigned mi;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002185 pj_bool_t got_media = PJ_FALSE;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002186 pj_status_t status = PJ_SUCCESS;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002187
2188 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
2189 return PJ_EBUSY;
2190
2191 /* Destroy existing media session, if any. */
2192 stop_media_session(call->index);
2193
2194 /* Reset audio_idx first */
2195 call->audio_idx = -1;
2196
2197 /* Process each media stream */
2198 for (mi=0; mi < call->med_cnt; ++mi) {
2199 pjsua_call_media *call_med = &call->media[mi];
2200
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002201 if (mi >= local_sdp->media_count ||
2202 mi >= remote_sdp->media_count)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002203 {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002204 /* This may happen when remote removed any SDP media lines in
2205 * its re-offer.
2206 */
2207 continue;
2208#if 0
Benny Prijono0bc99a92011-03-17 04:34:43 +00002209 /* Something is wrong */
2210 PJ_LOG(1,(THIS_FILE, "Error updating media for call %d: "
2211 "invalid media index %d in SDP", call_id, mi));
2212 return PJMEDIA_SDP_EINSDP;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002213#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002214 }
2215
2216 switch (call_med->type) {
2217 case PJMEDIA_TYPE_AUDIO:
2218 status = audio_channel_update(call_med, tmp_pool,
2219 local_sdp, remote_sdp);
2220 if (call->audio_idx==-1 && status==PJ_SUCCESS &&
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002221 call_med->strm.a.stream)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002222 {
2223 call->audio_idx = mi;
2224 }
2225 break;
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002226#if PJMEDIA_HAS_VIDEO
Benny Prijono0bc99a92011-03-17 04:34:43 +00002227 case PJMEDIA_TYPE_VIDEO:
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002228 status = video_channel_update(call_med, tmp_pool,
2229 local_sdp, remote_sdp);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002230 break;
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002231#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002232 default:
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002233 status = PJMEDIA_EINVALIMEDIATYPE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002234 break;
2235 }
2236
2237 if (status != PJ_SUCCESS) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002238 PJ_PERROR(1,(THIS_FILE, status, "Error updating media call%02d:%d",
Benny Prijono0bc99a92011-03-17 04:34:43 +00002239 call_id, mi));
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002240 } else {
2241 got_media = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002242 }
2243 }
2244
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002245 return (got_media? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002246}
2247
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002248/*
2249 * Get maxinum number of conference ports.
2250 */
2251PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
2252{
2253 return pjsua_var.media_cfg.max_media_ports;
2254}
2255
2256
2257/*
2258 * Get current number of active ports in the bridge.
2259 */
2260PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
2261{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002262 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002263 unsigned count = PJ_ARRAY_SIZE(ports);
2264 pj_status_t status;
2265
2266 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
2267 if (status != PJ_SUCCESS)
2268 count = 0;
2269
2270 return count;
2271}
2272
2273
2274/*
2275 * Enumerate all conference ports.
2276 */
2277PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
2278 unsigned *count)
2279{
2280 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
2281}
2282
2283
2284/*
2285 * Get information about the specified conference port
2286 */
2287PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
2288 pjsua_conf_port_info *info)
2289{
2290 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00002291 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002292 pj_status_t status;
2293
2294 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
2295 if (status != PJ_SUCCESS)
2296 return status;
2297
Benny Prijonoac623b32006-07-03 15:19:31 +00002298 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002299 info->slot_id = id;
2300 info->name = cinfo.name;
2301 info->clock_rate = cinfo.clock_rate;
2302 info->channel_count = cinfo.channel_count;
2303 info->samples_per_frame = cinfo.samples_per_frame;
2304 info->bits_per_sample = cinfo.bits_per_sample;
2305
2306 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00002307 info->listener_cnt = cinfo.listener_cnt;
2308 for (i=0; i<cinfo.listener_cnt; ++i) {
2309 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002310 }
2311
2312 return PJ_SUCCESS;
2313}
2314
2315
2316/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00002317 * Add arbitrary media port to PJSUA's conference bridge.
2318 */
2319PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
2320 pjmedia_port *port,
2321 pjsua_conf_port_id *p_id)
2322{
2323 pj_status_t status;
2324
2325 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
2326 port, NULL, (unsigned*)p_id);
2327 if (status != PJ_SUCCESS) {
2328 if (p_id)
2329 *p_id = PJSUA_INVALID_ID;
2330 }
2331
2332 return status;
2333}
2334
2335
2336/*
2337 * Remove arbitrary slot from the conference bridge.
2338 */
2339PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
2340{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002341 pj_status_t status;
2342
2343 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
2344 check_snd_dev_idle();
2345
2346 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00002347}
2348
2349
2350/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002351 * Establish unidirectional media flow from souce to sink.
2352 */
2353PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
2354 pjsua_conf_port_id sink)
2355{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002356 /* If sound device idle timer is active, cancel it first. */
Benny Prijono0f711b42009-05-06 19:08:43 +00002357 PJSUA_LOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002358 if (pjsua_var.snd_idle_timer.id) {
2359 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
2360 pjsua_var.snd_idle_timer.id = PJ_FALSE;
2361 }
Benny Prijono0f711b42009-05-06 19:08:43 +00002362 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002363
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002364
Benny Prijonof798e502009-03-09 13:08:16 +00002365 /* For audio switchboard (i.e. APS-Direct):
2366 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002367 * (format, clock rate, channel count) must match to peer's.
2368 * Note that sound device can be reopened only if it doesn't have
2369 * any connection.
2370 */
Benny Prijonof798e502009-03-09 13:08:16 +00002371 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002372 pjmedia_conf_port_info port0_info;
2373 pjmedia_conf_port_info peer_info;
2374 unsigned peer_id;
2375 pj_bool_t need_reopen = PJ_FALSE;
2376 pj_status_t status;
2377
2378 peer_id = (source!=0)? source : sink;
2379 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
2380 &peer_info);
2381 pj_assert(status == PJ_SUCCESS);
2382
2383 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
2384 pj_assert(status == PJ_SUCCESS);
2385
2386 /* Check if sound device is instantiated. */
2387 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2388 !pjsua_var.no_snd);
2389
2390 /* Check if sound device need to reopen because it needs to modify
2391 * settings to match its peer. Sound device must be idle in this case
2392 * though.
2393 */
2394 if (!need_reopen &&
2395 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
2396 {
2397 need_reopen = (peer_info.format.id != port0_info.format.id ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002398 peer_info.format.det.aud.avg_bps !=
2399 port0_info.format.det.aud.avg_bps ||
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002400 peer_info.clock_rate != port0_info.clock_rate ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002401 peer_info.channel_count!=port0_info.channel_count);
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002402 }
2403
2404 if (need_reopen) {
Benny Prijonod65f78c2009-06-03 18:59:37 +00002405 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
2406 pjmedia_aud_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002407
Benny Prijonod65f78c2009-06-03 18:59:37 +00002408 /* Create parameter based on peer info */
2409 status = create_aud_param(&param, pjsua_var.cap_dev,
2410 pjsua_var.play_dev,
2411 peer_info.clock_rate,
2412 peer_info.channel_count,
2413 peer_info.samples_per_frame,
2414 peer_info.bits_per_sample);
2415 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002416 pjsua_perror(THIS_FILE, "Error opening sound device",
2417 status);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002418 return status;
2419 }
Benny Prijonof798e502009-03-09 13:08:16 +00002420
Benny Prijonod65f78c2009-06-03 18:59:37 +00002421 /* And peer format */
2422 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
2423 param.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
2424 param.ext_fmt = peer_info.format;
2425 }
Benny Prijono10454dc2009-02-21 14:21:59 +00002426
Benny Prijonod65f78c2009-06-03 18:59:37 +00002427 status = open_snd_dev(&param);
2428 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002429 pjsua_perror(THIS_FILE, "Error opening sound device",
2430 status);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002431 return status;
2432 }
2433 } else {
2434 /* Null-audio */
Benny Prijonoc45d9512010-12-10 11:04:30 +00002435 status = pjsua_set_snd_dev(pjsua_var.cap_dev,
2436 pjsua_var.play_dev);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002437 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002438 pjsua_perror(THIS_FILE, "Error opening sound device",
2439 status);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002440 return status;
2441 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002442 }
Benny Prijono2d647722011-07-13 03:05:22 +00002443 } else if (pjsua_var.no_snd) {
2444 if (!pjsua_var.snd_is_on) {
2445 pjsua_var.snd_is_on = PJ_TRUE;
2446 /* Notify app */
2447 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2448 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2449 }
2450 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002451 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002452
Benny Prijonof798e502009-03-09 13:08:16 +00002453 } else {
2454 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002455
Benny Prijonof798e502009-03-09 13:08:16 +00002456 /* Create sound port if none is instantiated */
2457 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2458 !pjsua_var.no_snd)
2459 {
2460 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002461
Benny Prijonof798e502009-03-09 13:08:16 +00002462 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
2463 if (status != PJ_SUCCESS) {
2464 pjsua_perror(THIS_FILE, "Error opening sound device", status);
2465 return status;
2466 }
Benny Prijono2d647722011-07-13 03:05:22 +00002467 } else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) {
2468 pjsua_var.snd_is_on = PJ_TRUE;
2469 /* Notify app */
2470 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2471 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2472 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002473 }
Benny Prijonof798e502009-03-09 13:08:16 +00002474 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002475
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002476 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
2477}
2478
2479
2480/*
2481 * Disconnect media flow from the source to destination port.
2482 */
2483PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
2484 pjsua_conf_port_id sink)
2485{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002486 pj_status_t status;
2487
2488 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002489 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002490
2491 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002492}
2493
2494
Benny Prijono6dd967c2006-12-26 02:27:14 +00002495/*
2496 * Adjust the signal level to be transmitted from the bridge to the
2497 * specified port by making it louder or quieter.
2498 */
2499PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
2500 float level)
2501{
2502 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
2503 (int)((level-1) * 128));
2504}
2505
2506/*
2507 * Adjust the signal level to be received from the specified port (to
2508 * the bridge) by making it louder or quieter.
2509 */
2510PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
2511 float level)
2512{
2513 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
2514 (int)((level-1) * 128));
2515}
2516
2517
2518/*
2519 * Get last signal level transmitted to or received from the specified port.
2520 */
2521PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
2522 unsigned *tx_level,
2523 unsigned *rx_level)
2524{
2525 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
2526 tx_level, rx_level);
2527}
2528
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002529/*****************************************************************************
2530 * File player.
2531 */
2532
Benny Prijonod5696da2007-07-17 16:25:45 +00002533static char* get_basename(const char *path, unsigned len)
2534{
2535 char *p = ((char*)path) + len;
2536
2537 if (len==0)
2538 return p;
2539
Benny Prijono1f61a8f2007-08-16 10:11:44 +00002540 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00002541
2542 return (p==path) ? p : p+1;
2543}
2544
2545
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002546/*
2547 * Create a file player, and automatically connect this player to
2548 * the conference bridge.
2549 */
2550PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
2551 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002552 pjsua_player_id *p_id)
2553{
2554 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002555 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00002556 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002557 pjmedia_port *port;
2558 pj_status_t status;
2559
2560 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2561 return PJ_ETOOMANY;
2562
2563 PJSUA_LOCK();
2564
2565 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2566 if (pjsua_var.player[file_id].port == NULL)
2567 break;
2568 }
2569
2570 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2571 /* This is unexpected */
2572 PJSUA_UNLOCK();
2573 pj_assert(0);
2574 return PJ_EBUG;
2575 }
2576
2577 pj_memcpy(path, filename->ptr, filename->slen);
2578 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00002579
2580 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2581 if (!pool) {
2582 PJSUA_UNLOCK();
2583 return PJ_ENOMEM;
2584 }
2585
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00002586 status = pjmedia_wav_player_port_create(
2587 pool, path,
2588 pjsua_var.mconf_cfg.samples_per_frame *
Benny Prijonoc45d9512010-12-10 11:04:30 +00002589 1000 / pjsua_var.media_cfg.channel_count /
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00002590 pjsua_var.media_cfg.clock_rate,
2591 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002592 if (status != PJ_SUCCESS) {
2593 PJSUA_UNLOCK();
2594 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002595 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002596 return status;
2597 }
2598
Benny Prijono5297af92008-03-18 13:40:40 +00002599 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002600 port, filename, &slot);
2601 if (status != PJ_SUCCESS) {
2602 pjmedia_port_destroy(port);
2603 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00002604 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
2605 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002606 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002607 return status;
2608 }
2609
Benny Prijonoa66c3312007-01-21 23:12:40 +00002610 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00002611 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002612 pjsua_var.player[file_id].port = port;
2613 pjsua_var.player[file_id].slot = slot;
2614
2615 if (p_id) *p_id = file_id;
2616
2617 ++pjsua_var.player_cnt;
2618
2619 PJSUA_UNLOCK();
2620 return PJ_SUCCESS;
2621}
2622
2623
2624/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00002625 * Create a file playlist media port, and automatically add the port
2626 * to the conference bridge.
2627 */
2628PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
2629 unsigned file_count,
2630 const pj_str_t *label,
2631 unsigned options,
2632 pjsua_player_id *p_id)
2633{
2634 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00002635 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002636 pjmedia_port *port;
2637 pj_status_t status;
2638
2639 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2640 return PJ_ETOOMANY;
2641
2642 PJSUA_LOCK();
2643
2644 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2645 if (pjsua_var.player[file_id].port == NULL)
2646 break;
2647 }
2648
2649 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2650 /* This is unexpected */
2651 PJSUA_UNLOCK();
2652 pj_assert(0);
2653 return PJ_EBUG;
2654 }
2655
2656
2657 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
2658 pjsua_var.media_cfg.clock_rate;
2659
Benny Prijonod5696da2007-07-17 16:25:45 +00002660 pool = pjsua_pool_create("playlist", 1000, 1000);
2661 if (!pool) {
2662 PJSUA_UNLOCK();
2663 return PJ_ENOMEM;
2664 }
2665
2666 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002667 file_names, file_count,
2668 ptime, options, 0, &port);
2669 if (status != PJ_SUCCESS) {
2670 PJSUA_UNLOCK();
2671 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002672 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002673 return status;
2674 }
2675
Benny Prijonod5696da2007-07-17 16:25:45 +00002676 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002677 port, &port->info.name, &slot);
2678 if (status != PJ_SUCCESS) {
2679 pjmedia_port_destroy(port);
2680 PJSUA_UNLOCK();
2681 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002682 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002683 return status;
2684 }
2685
2686 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00002687 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002688 pjsua_var.player[file_id].port = port;
2689 pjsua_var.player[file_id].slot = slot;
2690
2691 if (p_id) *p_id = file_id;
2692
2693 ++pjsua_var.player_cnt;
2694
2695 PJSUA_UNLOCK();
2696 return PJ_SUCCESS;
2697
2698}
2699
2700
2701/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002702 * Get conference port ID associated with player.
2703 */
2704PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
2705{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002706 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002707 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2708
2709 return pjsua_var.player[id].slot;
2710}
2711
Benny Prijono469b1522006-12-26 03:05:17 +00002712/*
2713 * Get the media port for the player.
2714 */
Benny Prijonobe41d862008-01-18 13:24:28 +00002715PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00002716 pjmedia_port **p_port)
2717{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002718 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002719 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2720 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2721
2722 *p_port = pjsua_var.player[id].port;
2723
2724 return PJ_SUCCESS;
2725}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002726
2727/*
2728 * Set playback position.
2729 */
2730PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
2731 pj_uint32_t samples)
2732{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002733 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002734 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002735 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002736
2737 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
2738}
2739
2740
2741/*
2742 * Close the file, remove the player from the bridge, and free
2743 * resources associated with the file player.
2744 */
2745PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
2746{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002747 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002748 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2749
2750 PJSUA_LOCK();
2751
2752 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002753 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002754 pjmedia_port_destroy(pjsua_var.player[id].port);
2755 pjsua_var.player[id].port = NULL;
2756 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002757 pj_pool_release(pjsua_var.player[id].pool);
2758 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002759 pjsua_var.player_cnt--;
2760 }
2761
2762 PJSUA_UNLOCK();
2763
2764 return PJ_SUCCESS;
2765}
2766
2767
2768/*****************************************************************************
2769 * File recorder.
2770 */
2771
2772/*
2773 * Create a file recorder, and automatically connect this recorder to
2774 * the conference bridge.
2775 */
2776PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00002777 unsigned enc_type,
2778 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002779 pj_ssize_t max_size,
2780 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002781 pjsua_recorder_id *p_id)
2782{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002783 enum Format
2784 {
2785 FMT_UNKNOWN,
2786 FMT_WAV,
2787 FMT_MP3,
2788 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002789 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002790 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002791 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00002792 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00002793 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002794 pjmedia_port *port;
2795 pj_status_t status;
2796
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002797 /* Filename must present */
2798 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
2799
Benny Prijono00cae612006-07-31 15:19:36 +00002800 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002801 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002802
Benny Prijono8f310522006-10-20 11:08:49 +00002803 /* Don't support encoding type at present */
2804 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002805
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002806 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
2807 return PJ_ETOOMANY;
2808
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002809 /* Determine the file format */
2810 ext.ptr = filename->ptr + filename->slen - 4;
2811 ext.slen = 4;
2812
2813 if (pj_stricmp2(&ext, ".wav") == 0)
2814 file_format = FMT_WAV;
2815 else if (pj_stricmp2(&ext, ".mp3") == 0)
2816 file_format = FMT_MP3;
2817 else {
2818 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
2819 "determine file format for %.*s",
2820 (int)filename->slen, filename->ptr));
2821 return PJ_ENOTSUP;
2822 }
2823
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002824 PJSUA_LOCK();
2825
2826 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
2827 if (pjsua_var.recorder[file_id].port == NULL)
2828 break;
2829 }
2830
2831 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
2832 /* This is unexpected */
2833 PJSUA_UNLOCK();
2834 pj_assert(0);
2835 return PJ_EBUG;
2836 }
2837
2838 pj_memcpy(path, filename->ptr, filename->slen);
2839 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002840
Benny Prijonod5696da2007-07-17 16:25:45 +00002841 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2842 if (!pool) {
2843 PJSUA_UNLOCK();
2844 return PJ_ENOMEM;
2845 }
2846
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002847 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00002848 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002849 pjsua_var.media_cfg.clock_rate,
2850 pjsua_var.mconf_cfg.channel_count,
2851 pjsua_var.mconf_cfg.samples_per_frame,
2852 pjsua_var.mconf_cfg.bits_per_sample,
2853 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002854 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00002855 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002856 port = NULL;
2857 status = PJ_ENOTSUP;
2858 }
2859
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002860 if (status != PJ_SUCCESS) {
2861 PJSUA_UNLOCK();
2862 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002863 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002864 return status;
2865 }
2866
Benny Prijonod5696da2007-07-17 16:25:45 +00002867 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002868 port, filename, &slot);
2869 if (status != PJ_SUCCESS) {
2870 pjmedia_port_destroy(port);
2871 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00002872 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002873 return status;
2874 }
2875
2876 pjsua_var.recorder[file_id].port = port;
2877 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00002878 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002879
2880 if (p_id) *p_id = file_id;
2881
2882 ++pjsua_var.rec_cnt;
2883
2884 PJSUA_UNLOCK();
2885 return PJ_SUCCESS;
2886}
2887
2888
2889/*
2890 * Get conference port associated with recorder.
2891 */
2892PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
2893{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002894 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2895 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002896 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2897
2898 return pjsua_var.recorder[id].slot;
2899}
2900
Benny Prijono469b1522006-12-26 03:05:17 +00002901/*
2902 * Get the media port for the recorder.
2903 */
2904PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
2905 pjmedia_port **p_port)
2906{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002907 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2908 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002909 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2910 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2911
2912 *p_port = pjsua_var.recorder[id].port;
2913 return PJ_SUCCESS;
2914}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002915
2916/*
2917 * Destroy recorder (this will complete recording).
2918 */
2919PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
2920{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002921 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2922 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002923 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2924
2925 PJSUA_LOCK();
2926
2927 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002928 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002929 pjmedia_port_destroy(pjsua_var.recorder[id].port);
2930 pjsua_var.recorder[id].port = NULL;
2931 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002932 pj_pool_release(pjsua_var.recorder[id].pool);
2933 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002934 pjsua_var.rec_cnt--;
2935 }
2936
2937 PJSUA_UNLOCK();
2938
2939 return PJ_SUCCESS;
2940}
2941
2942
2943/*****************************************************************************
2944 * Sound devices.
2945 */
2946
2947/*
2948 * Enum sound devices.
2949 */
Benny Prijonof798e502009-03-09 13:08:16 +00002950
2951PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002952 unsigned *count)
2953{
2954 unsigned i, dev_count;
2955
Benny Prijono10454dc2009-02-21 14:21:59 +00002956 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002957
2958 if (dev_count > *count) dev_count = *count;
2959
2960 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00002961 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002962
Benny Prijono10454dc2009-02-21 14:21:59 +00002963 status = pjmedia_aud_dev_get_info(i, &info[i]);
2964 if (status != PJ_SUCCESS)
2965 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002966 }
2967
2968 *count = dev_count;
2969
2970 return PJ_SUCCESS;
2971}
Benny Prijonof798e502009-03-09 13:08:16 +00002972
2973
Benny Prijono10454dc2009-02-21 14:21:59 +00002974PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
2975 unsigned *count)
2976{
2977 unsigned i, dev_count;
2978
2979 dev_count = pjmedia_aud_dev_count();
2980
2981 if (dev_count > *count) dev_count = *count;
2982 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
2983
2984 for (i=0; i<dev_count; ++i) {
2985 pjmedia_aud_dev_info ai;
2986 pj_status_t status;
2987
2988 status = pjmedia_aud_dev_get_info(i, &ai);
2989 if (status != PJ_SUCCESS)
2990 return status;
2991
2992 strncpy(info[i].name, ai.name, sizeof(info[i].name));
2993 info[i].name[sizeof(info[i].name)-1] = '\0';
2994 info[i].input_count = ai.input_count;
2995 info[i].output_count = ai.output_count;
2996 info[i].default_samples_per_sec = ai.default_samples_per_sec;
2997 }
2998
2999 *count = dev_count;
3000
3001 return PJ_SUCCESS;
3002}
Benny Prijono10454dc2009-02-21 14:21:59 +00003003
Benny Prijonof798e502009-03-09 13:08:16 +00003004/* Create audio device parameter to open the device */
3005static pj_status_t create_aud_param(pjmedia_aud_param *param,
3006 pjmedia_aud_dev_index capture_dev,
3007 pjmedia_aud_dev_index playback_dev,
3008 unsigned clock_rate,
3009 unsigned channel_count,
3010 unsigned samples_per_frame,
3011 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003012{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003013 pj_status_t status;
3014
Benny Prijono96e74f32009-02-22 12:00:12 +00003015 /* Normalize device ID with new convention about default device ID */
3016 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
3017 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
3018
Benny Prijono10454dc2009-02-21 14:21:59 +00003019 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00003020 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00003021 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00003022 pjsua_perror(THIS_FILE, "Error retrieving default audio "
3023 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00003024 return status;
3025 }
Benny Prijonof798e502009-03-09 13:08:16 +00003026 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
3027 param->rec_id = capture_dev;
3028 param->play_id = playback_dev;
3029 param->clock_rate = clock_rate;
3030 param->channel_count = channel_count;
3031 param->samples_per_frame = samples_per_frame;
3032 param->bits_per_sample = bits_per_sample;
3033
3034 /* Update the setting with user preference */
3035#define update_param(cap, field) \
3036 if (pjsua_var.aud_param.flags & cap) { \
3037 param->flags |= cap; \
3038 param->field = pjsua_var.aud_param.field; \
3039 }
3040 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3041 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3042 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3043 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3044#undef update_param
3045
Benny Prijono10454dc2009-02-21 14:21:59 +00003046 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00003047 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
3048 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
3049 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
3050 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
3051
Benny Prijono10454dc2009-02-21 14:21:59 +00003052 /* EC settings */
3053 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00003054 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
3055 param->ec_enabled = PJ_TRUE;
3056 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00003057 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00003058 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00003059 }
3060
Benny Prijonof798e502009-03-09 13:08:16 +00003061 return PJ_SUCCESS;
3062}
Benny Prijono26056d82006-10-11 16:03:41 +00003063
Benny Prijonof798e502009-03-09 13:08:16 +00003064/* Internal: the first time the audio device is opened (during app
3065 * startup), retrieve the audio settings such as volume level
3066 * so that aud_get_settings() will work.
3067 */
3068static pj_status_t update_initial_aud_param()
3069{
3070 pjmedia_aud_stream *strm;
3071 pjmedia_aud_param param;
3072 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00003073
Benny Prijonof798e502009-03-09 13:08:16 +00003074 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00003075
Benny Prijonof798e502009-03-09 13:08:16 +00003076 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00003077
Benny Prijonof798e502009-03-09 13:08:16 +00003078 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003079 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00003080 pjsua_perror(THIS_FILE, "Error audio stream "
3081 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003082 return status;
3083 }
3084
Benny Prijonof798e502009-03-09 13:08:16 +00003085#define update_saved_param(cap, field) \
3086 if (param.flags & cap) { \
3087 pjsua_var.aud_param.flags |= cap; \
3088 pjsua_var.aud_param.field = param.field; \
3089 }
3090
3091 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3092 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3093 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3094 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3095#undef update_saved_param
3096
3097 return PJ_SUCCESS;
3098}
3099
3100/* Get format name */
3101static const char *get_fmt_name(pj_uint32_t id)
3102{
3103 static char name[8];
3104
3105 if (id == PJMEDIA_FORMAT_L16)
3106 return "PCM";
3107 pj_memcpy(name, &id, 4);
3108 name[4] = '\0';
3109 return name;
3110}
3111
3112/* Open sound device with the setting. */
3113static pj_status_t open_snd_dev(pjmedia_aud_param *param)
3114{
3115 pjmedia_port *conf_port;
3116 pj_status_t status;
3117
3118 PJ_ASSERT_RETURN(param, PJ_EINVAL);
3119
3120 /* Check if NULL sound device is used */
3121 if (NULL_SND_DEV_ID==param->rec_id || NULL_SND_DEV_ID==param->play_id) {
3122 return pjsua_set_null_snd_dev();
3123 }
3124
3125 /* Close existing sound port */
3126 close_snd_dev();
3127
Benny Prijono2d647722011-07-13 03:05:22 +00003128 /* Notify app */
3129 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3130 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
3131 }
3132
Benny Prijonof798e502009-03-09 13:08:16 +00003133 /* Create memory pool for sound device. */
3134 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3135 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3136
3137
3138 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
3139 get_fmt_name(param->ext_fmt.id),
3140 param->clock_rate, param->channel_count,
3141 param->samples_per_frame / param->channel_count * 1000 /
3142 param->clock_rate));
3143
3144 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
3145 param, &pjsua_var.snd_port);
3146 if (status != PJ_SUCCESS)
3147 return status;
3148
3149 /* Get the port0 of the conference bridge. */
3150 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3151 pj_assert(conf_port != NULL);
3152
3153 /* For conference bridge, resample if necessary if the bridge's
3154 * clock rate is different than the sound device's clock rate.
3155 */
3156 if (!pjsua_var.is_mswitch &&
3157 param->ext_fmt.id == PJMEDIA_FORMAT_PCM &&
Benny Prijonoc45d9512010-12-10 11:04:30 +00003158 PJMEDIA_PIA_SRATE(&conf_port->info) != param->clock_rate)
Benny Prijonof798e502009-03-09 13:08:16 +00003159 {
3160 pjmedia_port *resample_port;
3161 unsigned resample_opt = 0;
3162
3163 if (pjsua_var.media_cfg.quality >= 3 &&
3164 pjsua_var.media_cfg.quality <= 4)
3165 {
3166 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
3167 }
3168 else if (pjsua_var.media_cfg.quality < 3) {
3169 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
3170 }
3171
3172 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
3173 conf_port,
3174 param->clock_rate,
3175 resample_opt,
3176 &resample_port);
3177 if (status != PJ_SUCCESS) {
3178 char errmsg[PJ_ERR_MSG_SIZE];
3179 pj_strerror(status, errmsg, sizeof(errmsg));
3180 PJ_LOG(4, (THIS_FILE,
3181 "Error creating resample port: %s",
3182 errmsg));
3183 close_snd_dev();
3184 return status;
3185 }
3186
3187 conf_port = resample_port;
3188 }
3189
3190 /* Otherwise for audio switchboard, the switch's port0 setting is
3191 * derived from the sound device setting, so update the setting.
3192 */
3193 if (pjsua_var.is_mswitch) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00003194 pj_memcpy(&conf_port->info.fmt, &param->ext_fmt,
3195 sizeof(conf_port->info.fmt));
3196 conf_port->info.fmt.det.aud.clock_rate = param->clock_rate;
3197 conf_port->info.fmt.det.aud.frame_time_usec = param->samples_per_frame*
3198 1000000 /
3199 param->clock_rate;
3200 conf_port->info.fmt.det.aud.channel_count = param->channel_count;
3201 conf_port->info.fmt.det.aud.bits_per_sample = 16;
Benny Prijonof798e502009-03-09 13:08:16 +00003202 }
3203
Benny Prijonoc45d9512010-12-10 11:04:30 +00003204
Benny Prijonof798e502009-03-09 13:08:16 +00003205 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00003206 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
3207 conf_port );
3208 if (status != PJ_SUCCESS) {
3209 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
3210 "sound device", status);
3211 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3212 pjsua_var.snd_port = NULL;
3213 return status;
3214 }
3215
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003216 /* Save the device IDs */
Benny Prijonof798e502009-03-09 13:08:16 +00003217 pjsua_var.cap_dev = param->rec_id;
3218 pjsua_var.play_dev = param->play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003219
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003220 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00003221 {
3222 pjmedia_aud_dev_info rec_info;
3223 pjmedia_aud_stream *strm;
3224 pjmedia_aud_param si;
3225 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003226
Benny Prijonof798e502009-03-09 13:08:16 +00003227 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3228 status = pjmedia_aud_stream_get_param(strm, &si);
3229 if (status == PJ_SUCCESS)
3230 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00003231
Benny Prijonof798e502009-03-09 13:08:16 +00003232 if (status==PJ_SUCCESS) {
3233 if (param->clock_rate != pjsua_var.media_cfg.clock_rate) {
3234 char tmp_buf[128];
3235 int tmp_buf_len = sizeof(tmp_buf);
3236
3237 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
3238 "%s (%dKHz)",
3239 rec_info.name,
3240 param->clock_rate/1000);
3241 pj_strset(&tmp, tmp_buf, tmp_buf_len);
3242 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
3243 } else {
3244 pjmedia_conf_set_port0_name(pjsua_var.mconf,
3245 pj_cstr(&tmp, rec_info.name));
3246 }
3247 }
3248
3249 /* Any error is not major, let it through */
3250 status = PJ_SUCCESS;
3251 };
3252
3253 /* If this is the first time the audio device is open, retrieve some
3254 * settings from the device (such as volume settings) so that the
3255 * pjsua_snd_get_setting() work.
3256 */
3257 if (pjsua_var.aud_open_cnt == 0) {
3258 update_initial_aud_param();
3259 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00003260 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003261
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003262 return PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00003263}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003264
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003265
Benny Prijonof798e502009-03-09 13:08:16 +00003266/* Close existing sound device */
3267static void close_snd_dev(void)
3268{
Benny Prijono2d647722011-07-13 03:05:22 +00003269 /* Notify app */
3270 if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3271 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0);
3272 }
3273
Benny Prijonof798e502009-03-09 13:08:16 +00003274 /* Close sound device */
3275 if (pjsua_var.snd_port) {
3276 pjmedia_aud_dev_info cap_info, play_info;
3277 pjmedia_aud_stream *strm;
3278 pjmedia_aud_param param;
3279
3280 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3281 pjmedia_aud_stream_get_param(strm, &param);
3282
3283 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
3284 cap_info.name[0] = '\0';
3285 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
3286 play_info.name[0] = '\0';
3287
3288 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
3289 "%s sound capture device",
3290 play_info.name, cap_info.name));
3291
3292 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
3293 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3294 pjsua_var.snd_port = NULL;
3295 }
3296
3297 /* Close null sound device */
3298 if (pjsua_var.null_snd) {
3299 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
3300 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
3301 pjsua_var.null_snd = NULL;
3302 }
3303
3304 if (pjsua_var.snd_pool)
3305 pj_pool_release(pjsua_var.snd_pool);
3306 pjsua_var.snd_pool = NULL;
Benny Prijono2d647722011-07-13 03:05:22 +00003307 pjsua_var.snd_is_on = PJ_FALSE;
Benny Prijonof798e502009-03-09 13:08:16 +00003308}
3309
3310
3311/*
3312 * Select or change sound device. Application may call this function at
3313 * any time to replace current sound device.
3314 */
3315PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
3316 int playback_dev)
3317{
3318 unsigned alt_cr_cnt = 1;
3319 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
3320 unsigned i;
3321 pj_status_t status = -1;
3322
Benny Prijono23ea21a2009-06-03 12:43:06 +00003323 /* Null-sound */
3324 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
3325 return pjsua_set_null_snd_dev();
3326 }
3327
Benny Prijonof798e502009-03-09 13:08:16 +00003328 /* Set default clock rate */
3329 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
3330 if (alt_cr[0] == 0)
3331 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
3332
3333 /* Allow retrying of different clock rate if we're using conference
3334 * bridge (meaning audio format is always PCM), otherwise lock on
3335 * to one clock rate.
3336 */
3337 if (pjsua_var.is_mswitch) {
3338 alt_cr_cnt = 1;
3339 } else {
3340 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
3341 }
3342
3343 /* Attempts to open the sound device with different clock rates */
3344 for (i=0; i<alt_cr_cnt; ++i) {
3345 pjmedia_aud_param param;
3346 unsigned samples_per_frame;
3347
3348 /* Create the default audio param */
3349 samples_per_frame = alt_cr[i] *
3350 pjsua_var.media_cfg.audio_frame_ptime *
3351 pjsua_var.media_cfg.channel_count / 1000;
3352 status = create_aud_param(&param, capture_dev, playback_dev,
3353 alt_cr[i], pjsua_var.media_cfg.channel_count,
3354 samples_per_frame, 16);
3355 if (status != PJ_SUCCESS)
3356 return status;
3357
3358 /* Open! */
3359 status = open_snd_dev(&param);
3360 if (status == PJ_SUCCESS)
3361 break;
3362 }
3363
3364 if (status != PJ_SUCCESS) {
3365 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
3366 return status;
3367 }
3368
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003369 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00003370 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003371
Benny Prijonof798e502009-03-09 13:08:16 +00003372 return PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003373}
3374
3375
3376/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00003377 * Get currently active sound devices. If sound devices has not been created
3378 * (for example when pjsua_start() is not called), it is possible that
3379 * the function returns PJ_SUCCESS with -1 as device IDs.
3380 */
3381PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
3382 int *playback_dev)
3383{
3384 if (capture_dev) {
3385 *capture_dev = pjsua_var.cap_dev;
3386 }
3387 if (playback_dev) {
3388 *playback_dev = pjsua_var.play_dev;
3389 }
3390
3391 return PJ_SUCCESS;
3392}
3393
3394
3395/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003396 * Use null sound device.
3397 */
3398PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
3399{
3400 pjmedia_port *conf_port;
3401 pj_status_t status;
3402
3403 /* Close existing sound device */
3404 close_snd_dev();
3405
Benny Prijono2d647722011-07-13 03:05:22 +00003406 /* Notify app */
3407 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3408 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
3409 }
3410
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003411 /* Create memory pool for sound device. */
3412 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3413 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3414
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003415 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
3416
3417 /* Get the port0 of the conference bridge. */
3418 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3419 pj_assert(conf_port != NULL);
3420
3421 /* Create master port, connecting port0 of the conference bridge to
3422 * a null port.
3423 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003424 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003425 conf_port, 0, &pjsua_var.null_snd);
3426 if (status != PJ_SUCCESS) {
3427 pjsua_perror(THIS_FILE, "Unable to create null sound device",
3428 status);
3429 return status;
3430 }
3431
3432 /* Start the master port */
3433 status = pjmedia_master_port_start(pjsua_var.null_snd);
3434 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
3435
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003436 pjsua_var.cap_dev = NULL_SND_DEV_ID;
3437 pjsua_var.play_dev = NULL_SND_DEV_ID;
3438
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003439 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00003440 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003441
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003442 return PJ_SUCCESS;
3443}
3444
3445
Benny Prijonoe909eac2006-07-27 22:04:56 +00003446
3447/*
3448 * Use no device!
3449 */
3450PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
3451{
3452 /* Close existing sound device */
3453 close_snd_dev();
3454
3455 pjsua_var.no_snd = PJ_TRUE;
3456 return pjmedia_conf_get_master_port(pjsua_var.mconf);
3457}
3458
3459
Benny Prijonof20687a2006-08-04 18:27:19 +00003460/*
3461 * Configure the AEC settings of the sound port.
3462 */
Benny Prijono5da50432006-08-07 10:24:52 +00003463PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00003464{
3465 pjsua_var.media_cfg.ec_tail_len = tail_ms;
3466
3467 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00003468 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
3469 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00003470
3471 return PJ_SUCCESS;
3472}
3473
3474
3475/*
3476 * Get current AEC tail length.
3477 */
Benny Prijono22dfe592006-08-06 12:07:13 +00003478PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00003479{
3480 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
3481 return PJ_SUCCESS;
3482}
3483
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00003484
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003485/*
Benny Prijonof798e502009-03-09 13:08:16 +00003486 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003487 */
Benny Prijonof798e502009-03-09 13:08:16 +00003488PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003489{
Benny Prijonof798e502009-03-09 13:08:16 +00003490 return pjsua_var.snd_port != NULL;
3491}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003492
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003493
Benny Prijonof798e502009-03-09 13:08:16 +00003494/*
3495 * Configure sound device setting to the sound device being used.
3496 */
3497PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
3498 const void *pval,
3499 pj_bool_t keep)
3500{
Benny Prijono09b0ff62009-03-10 12:07:51 +00003501 pj_status_t status;
3502
Benny Prijonof798e502009-03-09 13:08:16 +00003503 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003504 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00003505 return PJMEDIA_EAUD_INVCAP;
3506 }
3507
Benny Prijono09b0ff62009-03-10 12:07:51 +00003508 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00003509 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00003510 pjmedia_aud_stream *strm;
3511
3512 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003513 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00003514 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00003515 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00003516 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00003517
3518 if (status != PJ_SUCCESS)
3519 return status;
3520
3521 /* Save in internal param for later device open */
3522 if (keep) {
3523 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
3524 cap, pval);
3525 }
3526
3527 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00003528}
3529
3530/*
3531 * Retrieve a sound device setting.
3532 */
3533PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
3534 void *pval)
3535{
3536 /* If sound device has never been opened before, open it to
3537 * retrieve the initial setting from the device (e.g. audio
3538 * volume)
3539 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003540 if (pjsua_var.aud_open_cnt==0) {
3541 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00003542 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003543 close_snd_dev();
3544 }
Benny Prijonof798e502009-03-09 13:08:16 +00003545
3546 if (pjsua_snd_is_active()) {
3547 /* Sound is active, retrieve from device directly */
3548 pjmedia_aud_stream *strm;
3549
3550 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3551 return pjmedia_aud_stream_get_cap(strm, cap, pval);
3552 } else {
3553 /* Otherwise retrieve from internal param */
3554 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
3555 cap, pval);
3556 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003557}
3558
3559
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003560/*****************************************************************************
3561 * Codecs.
3562 */
3563
3564/*
3565 * Enum all supported codecs in the system.
3566 */
3567PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
3568 unsigned *p_count )
3569{
3570 pjmedia_codec_mgr *codec_mgr;
3571 pjmedia_codec_info info[32];
3572 unsigned i, count, prio[32];
3573 pj_status_t status;
3574
3575 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3576 count = PJ_ARRAY_SIZE(info);
3577 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
3578 if (status != PJ_SUCCESS) {
3579 *p_count = 0;
3580 return status;
3581 }
3582
3583 if (count > *p_count) count = *p_count;
3584
3585 for (i=0; i<count; ++i) {
Nanang Izzuddin56b2ce42011-04-06 13:55:01 +00003586 pj_bzero(&id[i], sizeof(pjsua_codec_info));
3587
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003588 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
3589 id[i].codec_id = pj_str(id[i].buf_);
3590 id[i].priority = (pj_uint8_t) prio[i];
3591 }
3592
3593 *p_count = count;
3594
3595 return PJ_SUCCESS;
3596}
3597
3598
3599/*
3600 * Change codec priority.
3601 */
3602PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
3603 pj_uint8_t priority )
3604{
Benny Prijono88accae2008-06-26 15:48:14 +00003605 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003606 pjmedia_codec_mgr *codec_mgr;
3607
3608 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3609
Benny Prijono88accae2008-06-26 15:48:14 +00003610 if (codec_id->slen==1 && *codec_id->ptr=='*')
3611 codec_id = &all;
3612
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003613 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
3614 priority);
3615}
3616
3617
3618/*
3619 * Get codec parameters.
3620 */
3621PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
3622 pjmedia_codec_param *param )
3623{
Benny Prijono88accae2008-06-26 15:48:14 +00003624 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003625 const pjmedia_codec_info *info;
3626 pjmedia_codec_mgr *codec_mgr;
3627 unsigned count = 1;
3628 pj_status_t status;
3629
3630 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3631
Benny Prijono88accae2008-06-26 15:48:14 +00003632 if (codec_id->slen==1 && *codec_id->ptr=='*')
3633 codec_id = &all;
3634
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003635 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
3636 &count, &info, NULL);
3637 if (status != PJ_SUCCESS)
3638 return status;
3639
3640 if (count != 1)
Nanang Izzuddin50fae732011-03-22 09:49:23 +00003641 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003642
3643 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
3644 return status;
3645}
3646
3647
3648/*
3649 * Set codec parameters.
3650 */
Nanang Izzuddin06839e72010-01-27 11:48:31 +00003651PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003652 const pjmedia_codec_param *param)
3653{
Nanang Izzuddin06839e72010-01-27 11:48:31 +00003654 const pjmedia_codec_info *info[2];
3655 pjmedia_codec_mgr *codec_mgr;
3656 unsigned count = 2;
3657 pj_status_t status;
3658
3659 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3660
3661 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
3662 &count, info, NULL);
3663 if (status != PJ_SUCCESS)
3664 return status;
3665
3666 /* Codec ID should be specific, except for G.722.1 */
3667 if (count > 1 &&
3668 pj_strnicmp2(codec_id, "G7221/16", 8) != 0 &&
3669 pj_strnicmp2(codec_id, "G7221/32", 8) != 0)
3670 {
3671 pj_assert(!"Codec ID is not specific");
3672 return PJ_ETOOMANY;
3673 }
3674
3675 status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param);
3676 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003677}
Nanang Izzuddin50fae732011-03-22 09:49:23 +00003678
3679