blob: df2dd0787b1b0bfe0226629a0ba01b76122d4485 [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
Benny Prijono0bc99a92011-03-17 04:34:43 +0000278#if PJMEDIA_HAS_VIDEO
279 status = pjmedia_video_format_mgr_create(pjsua_var.pool, 64, 0, NULL);
280 if (status != PJ_SUCCESS) {
281 pjsua_perror(THIS_FILE, "Error creating PJMEDIA video format manager",
282 status);
283 return status;
284 }
285
286 status = pjmedia_converter_mgr_create(pjsua_var.pool, NULL);
287 if (status != PJ_SUCCESS) {
288 pjsua_perror(THIS_FILE, "Error creating PJMEDIA converter manager",
289 status);
290 return status;
291 }
292
293 status = pjmedia_vid_codec_mgr_create(pjsua_var.pool, NULL);
294 if (status != PJ_SUCCESS) {
295 pjsua_perror(THIS_FILE, "Error creating PJMEDIA video codec manager",
296 status);
297 return status;
298 }
299
300 status = pjmedia_vid_subsys_init(&pjsua_var.cp.factory);
301 if (status != PJ_SUCCESS) {
302 pjsua_perror(THIS_FILE, "Error creating PJMEDIA video subsystem",
303 status);
304 return status;
305 }
306#endif
307
308#if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_CODEC
309 /* Init ffmpeg video codecs */
310 status = pjmedia_codec_ffmpeg_init(NULL, &pjsua_var.cp.factory);
311 if (status != PJ_SUCCESS) {
312 pjsua_perror(THIS_FILE, "Error initializing ffmpeg library",
313 status);
314 return status;
315 }
316#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000317
318 /* Save additional conference bridge parameters for future
319 * reference.
320 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000321 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000322 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000323 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
324 pjsua_var.mconf_cfg.channel_count *
325 pjsua_var.media_cfg.audio_frame_ptime /
326 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000327
Benny Prijono0498d902006-06-19 14:49:14 +0000328 /* Init options for conference bridge. */
329 opt = PJMEDIA_CONF_NO_DEVICE;
330 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000331 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000332 {
333 opt |= PJMEDIA_CONF_SMALL_FILTER;
334 }
335 else if (pjsua_var.media_cfg.quality < 3) {
336 opt |= PJMEDIA_CONF_USE_LINEAR;
337 }
338
339
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000340 /* Init conference bridge. */
341 status = pjmedia_conf_create(pjsua_var.pool,
342 pjsua_var.media_cfg.max_media_ports,
343 pjsua_var.media_cfg.clock_rate,
344 pjsua_var.mconf_cfg.channel_count,
345 pjsua_var.mconf_cfg.samples_per_frame,
346 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000347 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000348 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000349 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000350 status);
351 return status;
352 }
353
Benny Prijonof798e502009-03-09 13:08:16 +0000354 /* Are we using the audio switchboard (a.k.a APS-Direct)? */
355 pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
356 ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
357
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000358 /* Create null port just in case user wants to use null sound. */
359 status = pjmedia_null_port_create(pjsua_var.pool,
360 pjsua_var.media_cfg.clock_rate,
361 pjsua_var.mconf_cfg.channel_count,
362 pjsua_var.mconf_cfg.samples_per_frame,
363 pjsua_var.mconf_cfg.bits_per_sample,
364 &pjsua_var.null_port);
365 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
366
Nanang Izzuddin69b69ae2009-04-14 15:18:30 +0000367#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
368 /* Initialize SRTP library. */
369 status = pjmedia_srtp_init_lib();
370 if (status != PJ_SUCCESS) {
371 pjsua_perror(THIS_FILE, "Error initializing SRTP library",
372 status);
373 return status;
374 }
375#endif
376
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000377 return PJ_SUCCESS;
378}
379
380
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000381/* Check if sound device is idle. */
382static void check_snd_dev_idle()
383{
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000384 unsigned call_cnt;
385
386 /* Get the call count, we shouldn't close the sound device when there is
387 * any calls active.
388 */
389 call_cnt = pjsua_call_get_count();
390
391 /* When this function is called from pjsua_media_channel_deinit() upon
392 * disconnecting call, actually the call count hasn't been updated/
393 * decreased. So we put additional check here, if there is only one
394 * call and it's in DISCONNECTED state, there is actually no active
395 * call.
396 */
397 if (call_cnt == 1) {
398 pjsua_call_id call_id;
399 pj_status_t status;
400
401 status = pjsua_enum_calls(&call_id, &call_cnt);
402 if (status == PJ_SUCCESS && call_cnt > 0 &&
403 !pjsua_call_is_active(call_id))
404 {
405 call_cnt = 0;
406 }
407 }
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000408
409 /* Activate sound device auto-close timer if sound device is idle.
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000410 * It is idle when there is no port connection in the bridge and
411 * there is no active call.
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000412 */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000413 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000414 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
415 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000416 call_cnt == 0 &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000417 pjsua_var.media_cfg.snd_auto_close_time >= 0)
418 {
419 pj_time_val delay;
420
421 delay.msec = 0;
422 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
423
424 pjsua_var.snd_idle_timer.id = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000425 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000426 &delay);
427 }
428}
429
430
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000431/* Timer callback to close sound device */
432static void close_snd_timer_cb( pj_timer_heap_t *th,
433 pj_timer_entry *entry)
434{
435 PJ_UNUSED_ARG(th);
436
Benny Prijono0f711b42009-05-06 19:08:43 +0000437 PJSUA_LOCK();
438 if (entry->id) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000439 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
Benny Prijono0f711b42009-05-06 19:08:43 +0000440 pjsua_var.media_cfg.snd_auto_close_time));
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000441
Benny Prijono0f711b42009-05-06 19:08:43 +0000442 entry->id = PJ_FALSE;
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000443
Benny Prijono0f711b42009-05-06 19:08:43 +0000444 close_snd_dev();
445 }
446 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000447}
448
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000449
450/*
451 * Start pjsua media subsystem.
452 */
453pj_status_t pjsua_media_subsys_start(void)
454{
455 pj_status_t status;
456
Benny Prijono0bc99a92011-03-17 04:34:43 +0000457#if DISABLED_FOR_TICKET_1185
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000458 /* Create media for calls, if none is specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000459 if (pjsua_var.calls[0].media[0].tp == NULL) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000460 pjsua_transport_config transport_cfg;
461
462 /* Create default transport config */
463 pjsua_transport_config_default(&transport_cfg);
464 transport_cfg.port = DEFAULT_RTP_PORT;
465
466 status = pjsua_media_transports_create(&transport_cfg);
467 if (status != PJ_SUCCESS)
468 return status;
469 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000470#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000471
Benny Prijono0bc99a92011-03-17 04:34:43 +0000472 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000473 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000474
Benny Prijonobf53b002010-01-04 13:08:31 +0000475 /* Perform NAT detection */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000476 status = pjsua_detect_nat_type();
477 if (status != PJ_SUCCESS) {
478 PJ_PERROR(1,(THIS_FILE, status, "NAT type detection failed"));
479 }
Benny Prijonobf53b002010-01-04 13:08:31 +0000480
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000481 return PJ_SUCCESS;
482}
483
484
485/*
486 * Destroy pjsua media subsystem.
487 */
488pj_status_t pjsua_media_subsys_destroy(void)
489{
490 unsigned i;
491
Benny Prijono384dab42009-10-14 01:58:04 +0000492 PJ_LOG(4,(THIS_FILE, "Shutting down media.."));
493
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000494 close_snd_dev();
495
496 if (pjsua_var.mconf) {
497 pjmedia_conf_destroy(pjsua_var.mconf);
498 pjsua_var.mconf = NULL;
499 }
500
501 if (pjsua_var.null_port) {
502 pjmedia_port_destroy(pjsua_var.null_port);
503 pjsua_var.null_port = NULL;
504 }
505
506 /* Destroy file players */
507 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
508 if (pjsua_var.player[i].port) {
509 pjmedia_port_destroy(pjsua_var.player[i].port);
510 pjsua_var.player[i].port = NULL;
511 }
512 }
513
514 /* Destroy file recorders */
515 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
516 if (pjsua_var.recorder[i].port) {
517 pjmedia_port_destroy(pjsua_var.recorder[i].port);
518 pjsua_var.recorder[i].port = NULL;
519 }
520 }
521
522 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000523 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000524 unsigned strm_idx;
525 pjsua_call *call = &pjsua_var.calls[i];
526 for (strm_idx=0; strm_idx<call->med_cnt; ++strm_idx) {
527 pjsua_call_media *call_med = &call->media[strm_idx];
528 if (call_med->tp_st != PJSUA_MED_TP_IDLE) {
529 pjsua_media_channel_deinit(i);
530 }
531 if (call_med->tp && call_med->tp_auto_del) {
532 pjmedia_transport_close(call_med->tp);
533 }
534 call_med->tp = NULL;
Benny Prijono311b63f2008-07-14 11:31:40 +0000535 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000536 }
537
538 /* Destroy media endpoint. */
539 if (pjsua_var.med_endpt) {
540
Benny Prijono0bc99a92011-03-17 04:34:43 +0000541 /* Videodev */
542# if PJMEDIA_HAS_VIDEO
543 pjmedia_vid_subsys_shutdown();
544# endif
545
546 /* ffmpeg */
547# if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_CODEC
548 pjmedia_codec_ffmpeg_deinit();
549# endif
550
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000551 /* Shutdown all codecs: */
552# if PJMEDIA_HAS_SPEEX_CODEC
553 pjmedia_codec_speex_deinit();
554# endif /* PJMEDIA_HAS_SPEEX_CODEC */
555
556# if PJMEDIA_HAS_GSM_CODEC
557 pjmedia_codec_gsm_deinit();
558# endif /* PJMEDIA_HAS_GSM_CODEC */
559
560# if PJMEDIA_HAS_G711_CODEC
561 pjmedia_codec_g711_deinit();
562# endif /* PJMEDIA_HAS_G711_CODEC */
563
Benny Prijono7ffd7752008-03-17 14:07:53 +0000564# if PJMEDIA_HAS_G722_CODEC
565 pjmedia_codec_g722_deinit();
566# endif /* PJMEDIA_HAS_G722_CODEC */
567
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000568# if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000569 pjmedia_codec_ipp_deinit();
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000570# endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000571
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000572# if PJMEDIA_HAS_PASSTHROUGH_CODECS
573 pjmedia_codec_passthrough_deinit();
574# endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
575
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000576# if PJMEDIA_HAS_G7221_CODEC
577 pjmedia_codec_g7221_deinit();
578# endif /* PJMEDIA_HAS_G7221_CODEC */
579
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000580# if PJMEDIA_HAS_L16_CODEC
581 pjmedia_codec_l16_deinit();
582# endif /* PJMEDIA_HAS_L16_CODEC */
583
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000584 pjmedia_endpt_destroy(pjsua_var.med_endpt);
585 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000586
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000587 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000588 // Not necessary, as pjmedia_snd_deinit() should have been called
589 // in pjmedia_endpt_destroy().
590 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000591 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000592
Benny Prijonode479562007-03-15 10:23:55 +0000593 /* Reset RTP port */
594 next_rtp_port = 0;
595
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000596 return PJ_SUCCESS;
597}
598
Benny Prijono0bc99a92011-03-17 04:34:43 +0000599/*
600 * Create RTP and RTCP socket pair, and possibly resolve their public
601 * address via STUN.
602 */
603static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
604 pjmedia_sock_info *skinfo)
605{
606 enum {
607 RTP_RETRY = 100
608 };
609 int i;
610 pj_sockaddr_in bound_addr;
611 pj_sockaddr_in mapped_addr[2];
612 pj_status_t status = PJ_SUCCESS;
613 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
614 pj_sock_t sock[2];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000615
Benny Prijono0bc99a92011-03-17 04:34:43 +0000616 /* Make sure STUN server resolution has completed */
617 status = resolve_stun_server(PJ_TRUE);
618 if (status != PJ_SUCCESS) {
619 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
620 return status;
621 }
622
623 if (next_rtp_port == 0)
624 next_rtp_port = (pj_uint16_t)cfg->port;
625
626 if (next_rtp_port == 0)
627 next_rtp_port = (pj_uint16_t)40000;
628
629 for (i=0; i<2; ++i)
630 sock[i] = PJ_INVALID_SOCKET;
631
632 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
633 if (cfg->bound_addr.slen) {
634 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
635 if (status != PJ_SUCCESS) {
636 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
637 status);
638 return status;
639 }
640 }
641
642 /* Loop retry to bind RTP and RTCP sockets. */
643 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
644
645 /* Create RTP socket. */
646 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
647 if (status != PJ_SUCCESS) {
648 pjsua_perror(THIS_FILE, "socket() error", status);
649 return status;
650 }
651
652 /* Apply QoS to RTP socket, if specified */
653 status = pj_sock_apply_qos2(sock[0], cfg->qos_type,
654 &cfg->qos_params,
655 2, THIS_FILE, "RTP socket");
656
657 /* Bind RTP socket */
658 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
659 next_rtp_port);
660 if (status != PJ_SUCCESS) {
661 pj_sock_close(sock[0]);
662 sock[0] = PJ_INVALID_SOCKET;
663 continue;
664 }
665
666 /* Create RTCP socket. */
667 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
668 if (status != PJ_SUCCESS) {
669 pjsua_perror(THIS_FILE, "socket() error", status);
670 pj_sock_close(sock[0]);
671 return status;
672 }
673
674 /* Apply QoS to RTCP socket, if specified */
675 status = pj_sock_apply_qos2(sock[1], cfg->qos_type,
676 &cfg->qos_params,
677 2, THIS_FILE, "RTCP socket");
678
679 /* Bind RTCP socket */
680 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
681 (pj_uint16_t)(next_rtp_port+1));
682 if (status != PJ_SUCCESS) {
683 pj_sock_close(sock[0]);
684 sock[0] = PJ_INVALID_SOCKET;
685
686 pj_sock_close(sock[1]);
687 sock[1] = PJ_INVALID_SOCKET;
688 continue;
689 }
690
691 /*
692 * If we're configured to use STUN, then find out the mapped address,
693 * and make sure that the mapped RTCP port is adjacent with the RTP.
694 */
695 if (pjsua_var.stun_srv.addr.sa_family != 0) {
696 char ip_addr[32];
697 pj_str_t stun_srv;
698
699 pj_ansi_strcpy(ip_addr,
700 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
701 stun_srv = pj_str(ip_addr);
702
703 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
704 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
705 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
706 mapped_addr);
707 if (status != PJ_SUCCESS) {
708 pjsua_perror(THIS_FILE, "STUN resolve error", status);
709 goto on_error;
710 }
711
712#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
713 if (pj_ntohs(mapped_addr[1].sin_port) ==
714 pj_ntohs(mapped_addr[0].sin_port)+1)
715 {
716 /* Success! */
717 break;
718 }
719
720 pj_sock_close(sock[0]);
721 sock[0] = PJ_INVALID_SOCKET;
722
723 pj_sock_close(sock[1]);
724 sock[1] = PJ_INVALID_SOCKET;
725#else
726 if (pj_ntohs(mapped_addr[1].sin_port) !=
727 pj_ntohs(mapped_addr[0].sin_port)+1)
728 {
729 PJ_LOG(4,(THIS_FILE,
730 "Note: STUN mapped RTCP port %d is not adjacent"
731 " to RTP port %d",
732 pj_ntohs(mapped_addr[1].sin_port),
733 pj_ntohs(mapped_addr[0].sin_port)));
734 }
735 /* Success! */
736 break;
737#endif
738
739 } else if (cfg->public_addr.slen) {
740
741 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
742 (pj_uint16_t)next_rtp_port);
743 if (status != PJ_SUCCESS)
744 goto on_error;
745
746 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
747 (pj_uint16_t)(next_rtp_port+1));
748 if (status != PJ_SUCCESS)
749 goto on_error;
750
751 break;
752
753 } else {
754
755 if (bound_addr.sin_addr.s_addr == 0) {
756 pj_sockaddr addr;
757
758 /* Get local IP address. */
759 status = pj_gethostip(pj_AF_INET(), &addr);
760 if (status != PJ_SUCCESS)
761 goto on_error;
762
763 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
764 }
765
766 for (i=0; i<2; ++i) {
767 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
768 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
769 }
770
771 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
772 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
773 break;
774 }
775 }
776
777 if (sock[0] == PJ_INVALID_SOCKET) {
778 PJ_LOG(1,(THIS_FILE,
779 "Unable to find appropriate RTP/RTCP ports combination"));
780 goto on_error;
781 }
782
783
784 skinfo->rtp_sock = sock[0];
785 pj_memcpy(&skinfo->rtp_addr_name,
786 &mapped_addr[0], sizeof(pj_sockaddr_in));
787
788 skinfo->rtcp_sock = sock[1];
789 pj_memcpy(&skinfo->rtcp_addr_name,
790 &mapped_addr[1], sizeof(pj_sockaddr_in));
791
792 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
793 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
794 sizeof(addr_buf), 3)));
795 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
796 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
797 sizeof(addr_buf), 3)));
798
799 next_rtp_port += 2;
800 return PJ_SUCCESS;
801
802on_error:
803 for (i=0; i<2; ++i) {
804 if (sock[i] != PJ_INVALID_SOCKET)
805 pj_sock_close(sock[i]);
806 }
807 return status;
808}
809
810/* Create normal UDP media transports */
811static pj_status_t create_udp_media_transport(const pjsua_transport_config *cfg,
812 pjsua_call_media *call_med)
813{
814 pjmedia_sock_info skinfo;
815 pj_status_t status;
816
817 status = create_rtp_rtcp_sock(cfg, &skinfo);
818 if (status != PJ_SUCCESS) {
819 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
820 status);
821 goto on_error;
822 }
823
824 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
825 &skinfo, 0, &call_med->tp);
826 if (status != PJ_SUCCESS) {
827 pjsua_perror(THIS_FILE, "Unable to create media transport",
828 status);
829 goto on_error;
830 }
831
832 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
833 pjsua_var.media_cfg.tx_drop_pct);
834
835 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
836 pjsua_var.media_cfg.rx_drop_pct);
837
838 return PJ_SUCCESS;
839
840on_error:
841 if (call_med->tp)
842 pjmedia_transport_close(call_med->tp);
843
844 return status;
845}
846
847#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +0000848/* Create normal UDP media transports */
849static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000850{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000851 unsigned i;
852 pj_status_t status;
853
Benny Prijono0bc99a92011-03-17 04:34:43 +0000854 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
855 pjsua_call *call = &pjsua_var.calls[i];
856 unsigned strm_idx;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000857
Benny Prijono0bc99a92011-03-17 04:34:43 +0000858 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
859 pjsua_call_media *call_med = &call->media[strm_idx];
860
861 status = create_udp_media_transport(cfg, &call_med->tp);
862 if (status != PJ_SUCCESS)
863 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000864 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000865 }
866
Benny Prijonoc97608e2007-03-23 16:34:20 +0000867 return PJ_SUCCESS;
868
869on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000870 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
871 pjsua_call *call = &pjsua_var.calls[i];
872 unsigned strm_idx;
873
874 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
875 pjsua_call_media *call_med = &call->media[strm_idx];
876
877 if (call_med->tp) {
878 pjmedia_transport_close(call_med->tp);
879 call_med->tp = NULL;
880 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000881 }
882 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000883 return status;
884}
Benny Prijono0bc99a92011-03-17 04:34:43 +0000885#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000886
Benny Prijono096c56c2007-09-15 08:30:16 +0000887/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000888static void on_ice_complete(pjmedia_transport *tp,
889 pj_ice_strans_op op,
890 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000891{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000892 pjsua_call_media *call_med = (pjsua_call_media*)tp->user_data;
Benny Prijono096c56c2007-09-15 08:30:16 +0000893
Benny Prijono0bc99a92011-03-17 04:34:43 +0000894 if (!call_med)
Benny Prijonof76e1392008-06-06 14:51:48 +0000895 return;
896
897 switch (op) {
898 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000899 call_med->tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000900 break;
901 case PJ_ICE_STRANS_OP_NEGOTIATION:
902 if (result != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000903 call_med->state = PJSUA_CALL_MEDIA_ERROR;
904 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijonof76e1392008-06-06 14:51:48 +0000905
Benny Prijono0bc99a92011-03-17 04:34:43 +0000906 if (call_med->call && pjsua_var.ua_cfg.cb.on_call_media_state) {
907 pjsua_var.ua_cfg.cb.on_call_media_state(call_med->call->index);
Benny Prijonof76e1392008-06-06 14:51:48 +0000908 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000909 } else if (call_med->call) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000910 /* Send UPDATE if default transport address is different than
911 * what was advertised (ticket #881)
912 */
913 pjmedia_transport_info tpinfo;
914 pjmedia_ice_transport_info *ii = NULL;
915 unsigned i;
916
917 pjmedia_transport_info_init(&tpinfo);
918 pjmedia_transport_get_info(tp, &tpinfo);
919 for (i=0; i<tpinfo.specific_info_cnt; ++i) {
920 if (tpinfo.spc_info[i].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
921 ii = (pjmedia_ice_transport_info*)
922 tpinfo.spc_info[i].buffer;
923 break;
924 }
925 }
926
927 if (ii && ii->role==PJ_ICE_SESS_ROLE_CONTROLLING &&
928 pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000929 &call_med->rtp_addr))
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000930 {
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000931 pj_bool_t use_update;
932 const pj_str_t STR_UPDATE = { "UPDATE", 6 };
933 pjsip_dialog_cap_status support_update;
934 pjsip_dialog *dlg;
935
Benny Prijono0bc99a92011-03-17 04:34:43 +0000936 dlg = call_med->call->inv->dlg;
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000937 support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW,
938 NULL, &STR_UPDATE);
939 use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED);
940
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000941 PJ_LOG(4,(THIS_FILE,
942 "ICE default transport address has changed for "
Benny Prijono0bc99a92011-03-17 04:34:43 +0000943 "call %d, sending %s", call_med->call->index,
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000944 (use_update ? "UPDATE" : "re-INVITE")));
945
946 if (use_update)
Benny Prijono0bc99a92011-03-17 04:34:43 +0000947 pjsua_call_update(call_med->call->index, 0, NULL);
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000948 else
Benny Prijono0bc99a92011-03-17 04:34:43 +0000949 pjsua_call_reinvite(call_med->call->index, 0, NULL);
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000950 }
Benny Prijonof76e1392008-06-06 14:51:48 +0000951 }
952 break;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000953 case PJ_ICE_STRANS_OP_KEEP_ALIVE:
954 if (result != PJ_SUCCESS) {
955 PJ_PERROR(4,(THIS_FILE, result,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000956 "ICE keep alive failure for transport %d:%d",
957 call_med->call->index, call_med->idx));
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000958 }
959 if (pjsua_var.ua_cfg.cb.on_ice_transport_error) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000960 pjsua_call_id id = call_med->call->index;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000961 (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result,
962 NULL);
963 }
964 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000965 }
966}
967
968
Benny Prijonof76e1392008-06-06 14:51:48 +0000969/* Parse "HOST:PORT" format */
970static pj_status_t parse_host_port(const pj_str_t *host_port,
971 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000972{
Benny Prijonof76e1392008-06-06 14:51:48 +0000973 pj_str_t str_port;
974
975 str_port.ptr = pj_strchr(host_port, ':');
976 if (str_port.ptr != NULL) {
977 int iport;
978
979 host->ptr = host_port->ptr;
980 host->slen = (str_port.ptr - host->ptr);
981 str_port.ptr++;
982 str_port.slen = host_port->slen - host->slen - 1;
983 iport = (int)pj_strtoul(&str_port);
984 if (iport < 1 || iport > 65535)
985 return PJ_EINVAL;
986 *port = (pj_uint16_t)iport;
987 } else {
988 *host = *host_port;
989 *port = 0;
990 }
991
992 return PJ_SUCCESS;
993}
994
995/* Create ICE media transports (when ice is enabled) */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000996static pj_status_t create_ice_media_transport(
997 const pjsua_transport_config *cfg,
998 pjsua_call_media *call_med)
Benny Prijonof76e1392008-06-06 14:51:48 +0000999{
1000 char stunip[PJ_INET6_ADDRSTRLEN];
1001 pj_ice_strans_cfg ice_cfg;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001002 pjmedia_ice_cb ice_cb;
1003 char name[32];
1004 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001005 pj_status_t status;
1006
Benny Prijonoda9785b2007-04-02 20:43:06 +00001007 /* Make sure STUN server resolution has completed */
Benny Prijonobb995fd2009-08-12 11:03:23 +00001008 status = resolve_stun_server(PJ_TRUE);
Benny Prijonoda9785b2007-04-02 20:43:06 +00001009 if (status != PJ_SUCCESS) {
1010 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
1011 return status;
1012 }
1013
Benny Prijonof76e1392008-06-06 14:51:48 +00001014 /* Create ICE stream transport configuration */
1015 pj_ice_strans_cfg_default(&ice_cfg);
1016 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
1017 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
1018 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
1019
1020 ice_cfg.af = pj_AF_INET();
1021 ice_cfg.resolver = pjsua_var.resolver;
1022
Benny Prijono329d6382009-05-29 13:04:03 +00001023 ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
1024
Benny Prijonof76e1392008-06-06 14:51:48 +00001025 /* Configure STUN settings */
1026 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
1027 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
1028 ice_cfg.stun.server = pj_str(stunip);
1029 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
1030 }
Benny Prijono329d6382009-05-29 13:04:03 +00001031 if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
1032 ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
Benny Prijonof76e1392008-06-06 14:51:48 +00001033
Benny Prijono4d79b0f2009-10-25 09:02:07 +00001034 /* Copy QoS setting to STUN setting */
1035 ice_cfg.stun.cfg.qos_type = cfg->qos_type;
1036 pj_memcpy(&ice_cfg.stun.cfg.qos_params, &cfg->qos_params,
1037 sizeof(cfg->qos_params));
1038
Benny Prijonof76e1392008-06-06 14:51:48 +00001039 /* Configure TURN settings */
1040 if (pjsua_var.media_cfg.enable_turn) {
1041 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
1042 &ice_cfg.turn.server,
1043 &ice_cfg.turn.port);
1044 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
1045 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
1046 return PJ_EINVAL;
1047 }
1048 if (ice_cfg.turn.port == 0)
1049 ice_cfg.turn.port = 3479;
1050 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
1051 pj_memcpy(&ice_cfg.turn.auth_cred,
1052 &pjsua_var.media_cfg.turn_auth_cred,
1053 sizeof(ice_cfg.turn.auth_cred));
Benny Prijono4d79b0f2009-10-25 09:02:07 +00001054
1055 /* Copy QoS setting to TURN setting */
1056 ice_cfg.turn.cfg.qos_type = cfg->qos_type;
1057 pj_memcpy(&ice_cfg.turn.cfg.qos_params, &cfg->qos_params,
1058 sizeof(cfg->qos_params));
Benny Prijonof76e1392008-06-06 14:51:48 +00001059 }
Benny Prijonob681a2f2007-03-25 18:44:51 +00001060
Benny Prijono0bc99a92011-03-17 04:34:43 +00001061 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
1062 ice_cb.on_ice_complete = &on_ice_complete;
1063 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", call_med->idx);
1064 call_med->tp_ready = PJ_EPENDING;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001065
Benny Prijono0bc99a92011-03-17 04:34:43 +00001066 comp_cnt = 1;
1067 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
1068 ++comp_cnt;
Benny Prijonof76e1392008-06-06 14:51:48 +00001069
Benny Prijono0bc99a92011-03-17 04:34:43 +00001070 status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
1071 &ice_cfg, &ice_cb, &call_med->tp);
1072 if (status != PJ_SUCCESS) {
1073 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
1074 status);
1075 goto on_error;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001076 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001077
Benny Prijono0bc99a92011-03-17 04:34:43 +00001078 /* Wait until transport is initialized, or time out */
1079 PJSUA_UNLOCK();
1080 while (call_med->tp_ready == PJ_EPENDING) {
1081 pjsua_handle_events(100);
1082 }
1083 PJSUA_LOCK();
1084 if (call_med->tp_ready != PJ_SUCCESS) {
1085 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
1086 call_med->tp_ready);
1087 status = call_med->tp_ready;
1088 goto on_error;
1089 }
1090
1091 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
1092 pjsua_var.media_cfg.tx_drop_pct);
1093
1094 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
1095 pjsua_var.media_cfg.rx_drop_pct);
1096
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001097 return PJ_SUCCESS;
1098
1099on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +00001100 if (call_med->tp != NULL) {
1101 pjmedia_transport_close(call_med->tp);
1102 call_med->tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001103 }
1104
Benny Prijonoc97608e2007-03-23 16:34:20 +00001105 return status;
1106}
1107
Benny Prijono0bc99a92011-03-17 04:34:43 +00001108#if DISABLED_FOR_TICKET_1185
1109/* Create ICE media transports (when ice is enabled) */
1110static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
1111{
1112 unsigned i;
1113 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001114
Benny Prijono0bc99a92011-03-17 04:34:43 +00001115 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
1116 pjsua_call *call = &pjsua_var.calls[i];
1117 unsigned strm_idx;
1118
1119 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1120 pjsua_call_media *call_med = &call->media[strm_idx];
1121
1122 status = create_ice_media_transport(cfg, call_med);
1123 if (status != PJ_SUCCESS)
1124 goto on_error;
1125 }
1126 }
1127
1128 return PJ_SUCCESS;
1129
1130on_error:
1131 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
1132 pjsua_call *call = &pjsua_var.calls[i];
1133 unsigned strm_idx;
1134
1135 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1136 pjsua_call_media *call_med = &call->media[strm_idx];
1137
1138 if (call_med->tp) {
1139 pjmedia_transport_close(call_med->tp);
1140 call_med->tp = NULL;
1141 }
1142 }
1143 }
1144 return status;
1145}
1146#endif
1147
1148#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +00001149/*
Benny Prijono0bc99a92011-03-17 04:34:43 +00001150 * Create media transports for all the calls. This function creates
Benny Prijonoc97608e2007-03-23 16:34:20 +00001151 * one UDP media transport for each call.
1152 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001153PJ_DEF(pj_status_t) pjsua_media_transports_create(
1154 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001155{
1156 pjsua_transport_config cfg;
1157 unsigned i;
1158 pj_status_t status;
1159
1160
1161 /* Make sure pjsua_init() has been called */
1162 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
1163
1164 PJSUA_LOCK();
1165
1166 /* Delete existing media transports */
1167 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001168 pjsua_call *call = &pjsua_var.calls[i];
1169 unsigned strm_idx;
1170
1171 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1172 pjsua_call_media *call_med = &call->media[strm_idx];
1173
1174 if (call_med->tp && call_med->tp_auto_del) {
1175 pjmedia_transport_close(call_med->tp);
1176 call_med->tp = NULL;
1177 call_med->tp_orig = NULL;
1178 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001179 }
1180 }
1181
1182 /* Copy config */
1183 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
1184
Benny Prijono40860c32008-09-04 13:55:33 +00001185 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001186 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijono4d79b0f2009-10-25 09:02:07 +00001187 status = create_ice_media_transports(&cfg);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001188 } else {
1189 status = create_udp_media_transports(&cfg);
1190 }
1191
Benny Prijono40860c32008-09-04 13:55:33 +00001192 /* Set media transport auto_delete to True */
1193 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001194 pjsua_call *call = &pjsua_var.calls[i];
1195 unsigned strm_idx;
1196
1197 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1198 pjsua_call_media *call_med = &call->media[strm_idx];
1199
1200 call_med->tp_auto_del = PJ_TRUE;
1201 }
Benny Prijono40860c32008-09-04 13:55:33 +00001202 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001203
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001204 PJSUA_UNLOCK();
1205
1206 return status;
1207}
1208
Benny Prijono40860c32008-09-04 13:55:33 +00001209/*
1210 * Attach application's created media transports.
1211 */
1212PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
1213 unsigned count,
1214 pj_bool_t auto_delete)
1215{
1216 unsigned i;
1217
1218 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
1219
1220 /* Assign the media transports */
1221 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001222 pjsua_call *call = &pjsua_var.calls[i];
1223 unsigned strm_idx;
1224
1225 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1226 pjsua_call_media *call_med = &call->media[strm_idx];
1227
1228 if (call_med->tp && call_med->tp_auto_del) {
1229 pjmedia_transport_close(call_med->tp);
1230 call_med->tp = NULL;
1231 call_med->tp_orig = NULL;
1232 }
Benny Prijono40860c32008-09-04 13:55:33 +00001233 }
1234
Benny Prijono0bc99a92011-03-17 04:34:43 +00001235 PJ_TODO(remove_pjsua_media_transports_attach);
1236
1237 call->media[0].tp = tp[i].transport;
1238 call->media[0].tp_auto_del = auto_delete;
Benny Prijono40860c32008-09-04 13:55:33 +00001239 }
1240
1241 return PJ_SUCCESS;
1242}
Benny Prijono0bc99a92011-03-17 04:34:43 +00001243#endif
Benny Prijono40860c32008-09-04 13:55:33 +00001244
Benny Prijono0bc99a92011-03-17 04:34:43 +00001245/* Go through the list of media in the SDP, find acceptable media, and
1246 * sort them based on the "quality" of the media, and store the indexes
1247 * in the specified array. Media with the best quality will be listed
1248 * first in the array. The quality factors considered currently is
1249 * encryption.
1250 */
1251static void sort_media(const pjmedia_sdp_session *sdp,
1252 const pj_str_t *type,
1253 pjmedia_srtp_use use_srtp,
1254 pj_uint8_t midx[],
1255 unsigned *p_count)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001256{
1257 unsigned i;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001258 unsigned count = 0;
1259 int score[PJSUA_MAX_CALL_MEDIA];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001260
Benny Prijono0bc99a92011-03-17 04:34:43 +00001261 pj_assert(*p_count >= PJSUA_MAX_CALL_MEDIA);
1262
1263 *p_count = 0;
Benny Prijono09c0d672011-04-11 05:03:24 +00001264 for (i=0; i<PJSUA_MAX_CALL_MEDIA; ++i)
1265 score[i] = 1;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001266
1267 /* Score each media */
1268 for (i=0; i<sdp->media_count && count<PJSUA_MAX_CALL_MEDIA; ++i) {
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001269 const pjmedia_sdp_media *m = sdp->media[i];
Nanang Izzuddina6414292011-04-08 04:26:18 +00001270 const pjmedia_sdp_conn *c;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001271
Benny Prijono0bc99a92011-03-17 04:34:43 +00001272 /* Skip different media */
1273 if (pj_stricmp(&m->desc.media, type) != 0) {
1274 score[count++] = -22000;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001275 continue;
1276 }
1277
Nanang Izzuddina6414292011-04-08 04:26:18 +00001278 c = m->conn? m->conn : sdp->conn;
1279
Benny Prijono0bc99a92011-03-17 04:34:43 +00001280 /* Supported transports */
1281 if (pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0) {
1282 switch (use_srtp) {
1283 case PJMEDIA_SRTP_MANDATORY:
1284 case PJMEDIA_SRTP_OPTIONAL:
1285 ++score[i];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001286 break;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001287 case PJMEDIA_SRTP_DISABLED:
1288 --score[i];
1289 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001290 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001291 } else if (pj_stricmp2(&m->desc.transport, "RTP/AVP")==0) {
1292 switch (use_srtp) {
1293 case PJMEDIA_SRTP_MANDATORY:
1294 --score[i];
1295 break;
1296 case PJMEDIA_SRTP_OPTIONAL:
1297 /* No change in score */
1298 break;
1299 case PJMEDIA_SRTP_DISABLED:
1300 ++score[i];
1301 break;
1302 }
1303 } else {
1304 score[i] -= 10;
1305 }
1306
1307 /* Is media disabled? */
1308 if (m->desc.port == 0)
1309 score[i] -= 10;
1310
1311 /* Is media inactive? */
Nanang Izzuddina6414292011-04-08 04:26:18 +00001312 if (pjmedia_sdp_media_find_attr2(m, "inactive", NULL) ||
1313 pj_strcmp2(&c->addr, "0.0.0.0") == 0)
1314 {
1315 //score[i] -= 10;
1316 score[i] -= 1;
1317 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001318
1319 ++count;
1320 }
1321
1322 /* Created sorted list based on quality */
1323 for (i=0; i<count; ++i) {
1324 unsigned j;
1325 int best = 0;
1326
1327 for (j=1; j<count; ++j) {
1328 if (score[j] > score[best])
1329 best = j;
1330 }
1331 /* Don't put media with negative score, that media is unacceptable
1332 * for us.
1333 */
1334 if (score[best] >= 0) {
1335 midx[*p_count] = (pj_uint8_t)best;
1336 (*p_count)++;
1337 }
1338
1339 score[best] = -22000;
1340
1341 }
1342}
1343
1344/* Initialize the media line */
1345static pj_status_t pjsua_call_media_init(pjsua_call_media *call_med,
1346 pjmedia_type type,
1347 const pjsua_transport_config *tcfg,
1348 int security_level,
1349 int *sip_err_code)
1350{
1351 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
1352 pj_status_t status;
1353
1354 /*
1355 * Note: this function may be called when the media already exists
1356 * (e.g. in reinvites, updates, etc.)
1357 */
1358 call_med->type = type;
1359
1360 /* Create the media transport for initial call. This is blocking for now */
1361 if (call_med->tp == NULL) {
1362 if (pjsua_var.media_cfg.enable_ice) {
1363 status = create_ice_media_transport(tcfg, call_med);
1364 } else {
1365 status = create_udp_media_transport(tcfg, call_med);
1366 }
1367
1368 if (status != PJ_SUCCESS) {
1369 PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport"));
1370 return status;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001371 }
1372 }
1373
Benny Prijono0bc99a92011-03-17 04:34:43 +00001374#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1375 /* This function may be called when SRTP transport already exists
1376 * (e.g: in re-invite, update), don't need to destroy/re-create.
1377 */
1378 if (!call_med->tp_orig || call_med->tp == call_med->tp_orig) {
1379 pjmedia_srtp_setting srtp_opt;
1380 pjmedia_transport *srtp = NULL;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001381
Benny Prijono0bc99a92011-03-17 04:34:43 +00001382 /* Check if SRTP requires secure signaling */
1383 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
1384 if (security_level < acc->cfg.srtp_secure_signaling) {
1385 if (sip_err_code)
1386 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1387 status = PJSIP_ESESSIONINSECURE;
1388 goto on_error;
1389 }
1390 }
1391
1392 /* Always create SRTP adapter */
1393 pjmedia_srtp_setting_default(&srtp_opt);
1394 srtp_opt.close_member_tp = PJ_FALSE;
1395 /* If media session has been ever established, let's use remote's
1396 * preference in SRTP usage policy, especially when it is stricter.
1397 */
1398 if (call_med->rem_srtp_use > acc->cfg.use_srtp)
1399 srtp_opt.use = call_med->rem_srtp_use;
1400 else
1401 srtp_opt.use = acc->cfg.use_srtp;
1402
1403 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
1404 call_med->tp,
1405 &srtp_opt, &srtp);
1406 if (status != PJ_SUCCESS) {
1407 if (sip_err_code)
1408 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
1409 goto on_error;
1410 }
1411
1412 /* Set SRTP as current media transport */
1413 call_med->tp_orig = call_med->tp;
1414 call_med->tp = srtp;
1415 }
1416#else
1417 call->tp_orig = call->tp;
1418 PJ_UNUSED_ARG(security_level);
1419#endif
1420
1421 return PJ_SUCCESS;
1422
1423on_error:
1424 if (call_med->tp) {
1425 pjmedia_transport_close(call_med->tp);
1426 call_med->tp = NULL;
1427 }
1428 return status;
1429}
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001430
Benny Prijonoc97608e2007-03-23 16:34:20 +00001431pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
Benny Prijonod8179652008-01-23 20:39:07 +00001432 pjsip_role_e role,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001433 int security_level,
Benny Prijono224b4e22008-06-19 14:10:28 +00001434 pj_pool_t *tmp_pool,
1435 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001436 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001437{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001438 const pj_str_t STR_AUDIO = { "audio", 5 };
1439 const pj_str_t STR_VIDEO = { "video", 5 };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001440 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijonod8179652008-01-23 20:39:07 +00001441 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001442 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1443 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
1444 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
1445 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
1446 pjmedia_type media_types[PJSUA_MAX_CALL_MEDIA];
1447 unsigned mi;
1448 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001449
Benny Prijonod8179652008-01-23 20:39:07 +00001450 PJ_UNUSED_ARG(role);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001451
Benny Prijono0bc99a92011-03-17 04:34:43 +00001452 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
1453 return PJ_EBUSY;
1454
1455#if DISABLED_FOR_TICKET_1185
Benny Prijonod8179652008-01-23 20:39:07 +00001456 /* Return error if media transport has not been created yet
1457 * (e.g. application is starting)
1458 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001459 for (i=0; i<call->med_cnt; ++i) {
1460 if (call->media[i].tp == NULL) {
1461 return PJ_EBUSY;
Benny Prijonod8179652008-01-23 20:39:07 +00001462 }
Benny Prijono53a7c702008-04-14 02:57:29 +00001463 }
Benny Prijonod8179652008-01-23 20:39:07 +00001464#endif
1465
Benny Prijono0bc99a92011-03-17 04:34:43 +00001466 if (rem_sdp) {
1467 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
1468 maudidx, &maudcnt);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001469
Benny Prijono0bc99a92011-03-17 04:34:43 +00001470 if (maudcnt==0) {
1471 /* Expecting audio in the offer */
1472 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1473 pjsua_media_channel_deinit(call_id);
1474 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
1475 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001476
Benny Prijono0bc99a92011-03-17 04:34:43 +00001477 sort_media(rem_sdp, &STR_VIDEO, acc->cfg.use_srtp,
1478 mvididx, &mvidcnt);
1479 mvidcnt = (mvidcnt < acc->cfg.max_video_cnt) ?
1480 mvidcnt : acc->cfg.max_video_cnt;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001481
Benny Prijono0bc99a92011-03-17 04:34:43 +00001482 } else {
1483 maudcnt = acc->cfg.max_audio_cnt;
1484 for (mi=0; mi<maudcnt; ++mi) {
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001485 maudidx[mi] = (pj_uint8_t)mi;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001486 media_types[mi] = PJMEDIA_TYPE_AUDIO;
1487 }
1488 mvidcnt = acc->cfg.max_video_cnt;
1489 for (mi=0; mi<mvidcnt; ++mi) {
1490 media_types[maudcnt + mi] = PJMEDIA_TYPE_VIDEO;
1491 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001492 }
1493
Benny Prijono0bc99a92011-03-17 04:34:43 +00001494 call->med_cnt = maudcnt + mvidcnt;
1495
1496 if (call->med_cnt == 0) {
1497 /* Expecting at least one media */
Benny Prijonoab8dba92008-06-27 21:59:15 +00001498 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001499 pjsua_media_channel_deinit(call_id);
Benny Prijonoab8dba92008-06-27 21:59:15 +00001500 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001501 }
1502
Benny Prijono0bc99a92011-03-17 04:34:43 +00001503 /* Initialize each media line */
1504 for (mi=0; mi < call->med_cnt; ++mi) {
1505 pjsua_call_media *call_med = &call->media[mi];
1506 pj_bool_t enabled = PJ_FALSE;
1507 pjmedia_type media_type = PJMEDIA_TYPE_NONE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001508
Benny Prijono0bc99a92011-03-17 04:34:43 +00001509 if (rem_sdp) {
1510 if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_AUDIO)) {
1511 media_type = PJMEDIA_TYPE_AUDIO;
1512 if (pj_memchr(maudidx, mi, maudcnt * sizeof(maudidx[0]))) {
1513 enabled = PJ_TRUE;
1514 }
1515 }
1516 else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_VIDEO)) {
1517 media_type = PJMEDIA_TYPE_VIDEO;
1518 if (pj_memchr(mvididx, mi, mvidcnt * sizeof(mvididx[0]))) {
1519 enabled = PJ_TRUE;
1520 }
1521 }
1522
1523 } else {
1524 enabled = PJ_TRUE;
1525 media_type = media_types[mi];
1526 }
1527
1528 if (enabled) {
1529 status = pjsua_call_media_init(call_med, media_type,
1530 &acc->cfg.rtp_cfg,
1531 security_level, sip_err_code);
1532 if (status != PJ_SUCCESS) {
1533 pjsua_media_channel_deinit(call_id);
1534 return status;
1535 }
1536 } else {
1537 /* By convention, the media is inactive if transport is NULL */
1538 if (call_med->tp) {
1539 pjmedia_transport_close(call_med->tp);
1540 call_med->tp = NULL;
1541 }
1542 }
Benny Prijono224b4e22008-06-19 14:10:28 +00001543 }
1544
Benny Prijono0bc99a92011-03-17 04:34:43 +00001545 call->audio_idx = maudidx[0];
1546
1547 PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d",
1548 call->audio_idx, call->index));
1549
1550 /* Tell the media transport of a new offer/answer session */
1551 for (mi=0; mi < call->med_cnt; ++mi) {
1552 pjsua_call_media *call_med = &call->media[mi];
1553
1554 /* Note: tp may be NULL if this media line is inactive */
1555 if (call_med->tp) {
1556 status = pjmedia_transport_media_create(call_med->tp,
1557 tmp_pool, 0,
1558 rem_sdp, mi);
1559 if (status != PJ_SUCCESS) {
1560 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1561 pjsua_media_channel_deinit(call_id);
1562 return status;
1563 }
1564
1565 call_med->tp_st = PJSUA_MED_TP_INIT;
1566 }
1567 }
1568
Benny Prijonoc97608e2007-03-23 16:34:20 +00001569 return PJ_SUCCESS;
1570}
1571
1572pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1573 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001574 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001575 pjmedia_sdp_session **p_sdp,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001576 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001577{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001578 const pj_str_t STR_AUDIO = { "audio", 5 };
1579 enum { MAX_MEDIA = PJSUA_MAX_CALL_MEDIA };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001580 pjmedia_sdp_session *sdp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001581 pj_sockaddr origin;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001582 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001583 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001584 pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001585 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001586 pj_status_t status;
1587
Benny Prijono0bc99a92011-03-17 04:34:43 +00001588 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
Benny Prijono55e82352007-05-10 20:49:08 +00001589 return PJ_EBUSY;
Benny Prijono55e82352007-05-10 20:49:08 +00001590
Benny Prijono0bc99a92011-03-17 04:34:43 +00001591 if (rem_sdp) {
1592 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1593 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001594
Benny Prijono0bc99a92011-03-17 04:34:43 +00001595 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
1596 maudidx, &maudcnt);
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001597
Benny Prijono0bc99a92011-03-17 04:34:43 +00001598 if (maudcnt==0) {
1599 /* Expecting audio in the offer */
1600 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1601 pjsua_media_channel_deinit(call_id);
1602 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijono0324ba52010-12-02 10:41:46 +00001603 }
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001604
Benny Prijono0bc99a92011-03-17 04:34:43 +00001605 call->audio_idx = maudidx[0];
1606 } else {
1607 /* Audio is first in our offer, by convention */
1608 call->audio_idx = 0;
1609 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001610
Benny Prijono224b4e22008-06-19 14:10:28 +00001611 /* Create media if it's not created. This could happen when call is
Benny Prijono0bc99a92011-03-17 04:34:43 +00001612 * currently on-hold (with the old style hold)
Benny Prijono224b4e22008-06-19 14:10:28 +00001613 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001614 if (call->media[call->audio_idx].tp == NULL) {
Benny Prijono224b4e22008-06-19 14:10:28 +00001615 pjsip_role_e role;
1616 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1617 status = pjsua_media_channel_init(call_id, role, call->secure_level,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001618 pool, rem_sdp, sip_err_code);
Benny Prijono224b4e22008-06-19 14:10:28 +00001619 if (status != PJ_SUCCESS)
1620 return status;
1621 }
1622
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001623 /* Get SDP negotiator state */
1624 if (call->inv && call->inv->neg)
1625 sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg);
1626
Benny Prijono0bc99a92011-03-17 04:34:43 +00001627 /* Get one address to use in the origin field */
1628 pj_bzero(&origin, sizeof(origin));
1629 for (mi=0; mi<call->med_cnt; ++mi) {
1630 pjmedia_transport_info tpinfo;
Benny Prijono617c5bc2007-04-02 19:51:21 +00001631
Benny Prijono0bc99a92011-03-17 04:34:43 +00001632 if (call->media[mi].tp == NULL)
1633 continue;
1634
1635 pjmedia_transport_info_init(&tpinfo);
1636 pjmedia_transport_get_info(call->media[mi].tp, &tpinfo);
1637 pj_sockaddr_cp(&origin, &tpinfo.sock_info.rtp_addr_name);
1638 break;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001639 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001640
Benny Prijono0bc99a92011-03-17 04:34:43 +00001641 /* Create the base (blank) SDP */
1642 status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, pool, NULL,
1643 &origin, &sdp);
1644 if (status != PJ_SUCCESS)
1645 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001646
Benny Prijono0bc99a92011-03-17 04:34:43 +00001647 /* Process each media line */
1648 for (mi=0; mi<call->med_cnt; ++mi) {
1649 pjsua_call_media *call_med = &call->media[mi];
1650 pjmedia_sdp_media *m = NULL;
1651 pjmedia_transport_info tpinfo;
1652
1653 if (call_med->tp == NULL) {
1654 /*
1655 * This media is deactivated. Just create a valid SDP with zero
1656 * port.
1657 */
1658 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1659 m->desc.transport = pj_str("RTP/AVP");
1660 m->desc.fmt_count = 1;
1661 m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
1662 m->conn->net_type = pj_str("IN");
1663 m->conn->addr_type = pj_str("IP4");
1664 m->conn->addr = pj_str("127.0.0.1");
1665
1666 switch (call_med->type) {
1667 case PJMEDIA_TYPE_AUDIO:
1668 m->desc.media = pj_str("audio");
1669 m->desc.fmt[0] = pj_str("0");
1670 break;
1671 case PJMEDIA_TYPE_VIDEO:
1672 m->desc.media = pj_str("video");
1673 m->desc.fmt[0] = pj_str("31");
1674 break;
1675 default:
1676 if (rem_sdp && mi < rem_sdp->media_count) {
1677 pj_strdup(pool, &m->desc.media,
1678 &rem_sdp->media[mi]->desc.media);
1679 pj_strdup(pool, &m->desc.fmt[0],
1680 &rem_sdp->media[mi]->desc.fmt[0]);
1681 } else {
1682 pj_assert(!"Invalid call_med media type");
1683 return PJ_EBUG;
1684 }
1685 }
1686
1687 sdp->media[sdp->media_count++] = m;
1688 continue;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001689 }
1690
Benny Prijono0bc99a92011-03-17 04:34:43 +00001691 /* Get transport address info */
1692 pjmedia_transport_info_init(&tpinfo);
1693 pjmedia_transport_get_info(call_med->tp, &tpinfo);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001694
Benny Prijono0bc99a92011-03-17 04:34:43 +00001695 /* Ask pjmedia endpoint to create SDP media line */
1696 switch (call_med->type) {
1697 case PJMEDIA_TYPE_AUDIO:
1698 status = pjmedia_endpt_create_audio_sdp(pjsua_var.med_endpt, pool,
1699 &tpinfo.sock_info, 0, &m);
1700 break;
1701 case PJMEDIA_TYPE_VIDEO:
1702 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
1703 &tpinfo.sock_info, 0, &m);
1704 break;
1705 default:
1706 pj_assert(!"Invalid call_med media type");
1707 return PJ_EBUG;
1708 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001709
Benny Prijono0bc99a92011-03-17 04:34:43 +00001710 if (status != PJ_SUCCESS)
1711 return status;
1712
1713 sdp->media[sdp->media_count++] = m;
1714
1715 /* Give to transport */
1716 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
1717 sdp, rem_sdp, mi);
1718 if (status != PJ_SUCCESS) {
1719 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1720 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001721 }
1722 }
1723
Benny Prijono6ba8c542007-10-16 01:34:14 +00001724 /* Add NAT info in the SDP */
1725 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1726 pjmedia_sdp_attr *a;
1727 pj_str_t value;
1728 char nat_info[80];
1729
1730 value.ptr = nat_info;
1731 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1732 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1733 "%d", pjsua_var.nat_type);
1734 } else {
1735 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1736 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1737 "%d %s",
1738 pjsua_var.nat_type,
1739 type_name);
1740 }
1741
1742 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1743
1744 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1745
1746 }
1747
Benny Prijonoc97608e2007-03-23 16:34:20 +00001748
Benny Prijono0bc99a92011-03-17 04:34:43 +00001749#if DISABLED_FOR_TICKET_1185 && defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001750 /* Check if SRTP is in optional mode and configured to use duplicated
1751 * media, i.e: secured and unsecured version, in the SDP offer.
1752 */
1753 if (!rem_sdp &&
1754 pjsua_var.acc[call->acc_id].cfg.use_srtp == PJMEDIA_SRTP_OPTIONAL &&
1755 pjsua_var.acc[call->acc_id].cfg.srtp_optional_dup_offer)
1756 {
1757 unsigned i;
1758
1759 for (i = 0; i < sdp->media_count; ++i) {
1760 pjmedia_sdp_media *m = sdp->media[i];
1761
Benny Prijono0bc99a92011-03-17 04:34:43 +00001762 /* Check if this media is unsecured but has SDP "crypto"
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001763 * attribute.
1764 */
1765 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") == 0 &&
1766 pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL)
1767 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001768 if (i == (unsigned)call->audio_idx &&
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001769 sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE)
1770 {
1771 /* This is a session update, and peer has chosen the
1772 * unsecured version, so let's make this unsecured too.
1773 */
1774 pjmedia_sdp_media_remove_all_attr(m, "crypto");
1775 } else {
1776 /* This is new offer, duplicate media so we'll have
1777 * secured (with "RTP/SAVP" transport) and and unsecured
1778 * versions.
1779 */
1780 pjmedia_sdp_media *new_m;
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001781
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001782 /* Duplicate this media and apply secured transport */
1783 new_m = pjmedia_sdp_media_clone(pool, m);
1784 pj_strdup2(pool, &new_m->desc.transport, "RTP/SAVP");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001785
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001786 /* Remove the "crypto" attribute in the unsecured media */
1787 pjmedia_sdp_media_remove_all_attr(m, "crypto");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001788
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001789 /* Insert the new media before the unsecured media */
1790 if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001791 pj_array_insert(sdp->media, sizeof(new_m),
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001792 sdp->media_count, i, &new_m);
1793 ++sdp->media_count;
1794 ++i;
1795 }
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001796 }
1797 }
1798 }
1799 }
1800#endif
1801
Benny Prijonoc97608e2007-03-23 16:34:20 +00001802 *p_sdp = sdp;
1803 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001804}
1805
1806
1807static void stop_media_session(pjsua_call_id call_id)
1808{
1809 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001810 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001811
Benny Prijono0bc99a92011-03-17 04:34:43 +00001812 for (mi=0; mi<call->med_cnt; ++mi) {
1813 pjsua_call_media *call_med = &call->media[mi];
1814
1815 if (call_med->type == PJMEDIA_TYPE_AUDIO) {
1816 pjmedia_stream *strm = call_med->strm.a.stream;
1817 pjmedia_rtcp_stat stat;
1818
1819 if (strm) {
1820 if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) {
1821 if (pjsua_var.mconf) {
1822 pjsua_conf_remove_port(call_med->strm.a.conf_slot);
1823 }
1824 call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
1825 }
1826
1827 if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
1828 (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS))
1829 {
1830 /* Save RTP timestamp & sequence, so when media session is
1831 * restarted, those values will be restored as the initial
1832 * RTP timestamp & sequence of the new media session. So in
1833 * the same call session, RTP timestamp and sequence are
1834 * guaranteed to be contigue.
1835 */
1836 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
1837 call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
1838 call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
1839 }
1840
1841 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1842 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, strm, mi);
1843 }
1844
1845 pjmedia_stream_destroy(strm);
1846 call_med->strm.a.stream = NULL;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001847 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00001848 }
1849
1850#if PJMEDIA_HAS_VIDEO
1851 else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001852 pjmedia_vid_stream *strm = call_med->strm.v.stream;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001853
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001854 if (strm) {
1855 pjmedia_rtcp_stat stat;
1856
1857 if (call_med->strm.v.capturer) {
1858 pjmedia_vid_port_stop(call_med->strm.v.capturer);
1859 pjmedia_vid_port_destroy(call_med->strm.v.capturer);
1860 call_med->strm.v.capturer = NULL;
1861 }
1862
1863 if (call_med->strm.v.renderer) {
1864 pjmedia_vid_port_stop(call_med->strm.v.renderer);
1865 pjmedia_vid_port_destroy(call_med->strm.v.renderer);
1866 call_med->strm.v.renderer = NULL;
1867 }
1868
1869 if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
1870 (pjmedia_vid_stream_get_stat(strm, &stat) == PJ_SUCCESS))
1871 {
1872 /* Save RTP timestamp & sequence, so when media session is
1873 * restarted, those values will be restored as the initial
1874 * RTP timestamp & sequence of the new media session. So in
1875 * the same call session, RTP timestamp and sequence are
1876 * guaranteed to be contigue.
1877 */
1878 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
1879 call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
1880 call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
1881 }
1882
1883 pjmedia_vid_stream_destroy(strm);
1884 call_med->strm.v.stream = NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001885 }
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001886 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00001887#endif
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001888
1889 PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed",
1890 call_id, mi));
Benny Prijono0bc99a92011-03-17 04:34:43 +00001891 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001892 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001893}
1894
1895pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1896{
1897 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001898 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001899
1900 stop_media_session(call_id);
1901
Benny Prijono0bc99a92011-03-17 04:34:43 +00001902 for (mi=0; mi<call->med_cnt; ++mi) {
1903 pjsua_call_media *call_med = &call->media[mi];
Benny Prijonoc97608e2007-03-23 16:34:20 +00001904
Benny Prijono0bc99a92011-03-17 04:34:43 +00001905 if (call_med->tp_st != PJSUA_MED_TP_IDLE) {
1906 pjmedia_transport_media_stop(call_med->tp);
1907 call_med->tp_st = PJSUA_MED_TP_IDLE;
1908 }
1909
1910 if (call_med->tp_orig && call_med->tp &&
1911 call_med->tp != call_med->tp_orig)
1912 {
1913 pjmedia_transport_close(call_med->tp);
1914 call_med->tp = call_med->tp_orig;
1915 }
Benny Prijonod8179652008-01-23 20:39:07 +00001916 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00001917
1918 check_snd_dev_idle();
1919
Benny Prijonoc97608e2007-03-23 16:34:20 +00001920 return PJ_SUCCESS;
1921}
1922
1923
1924/*
1925 * DTMF callback from the stream.
1926 */
1927static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1928 int digit)
1929{
1930 PJ_UNUSED_ARG(strm);
1931
Benny Prijono0c068262008-02-14 14:38:52 +00001932 /* For discussions about call mutex protection related to this
1933 * callback, please see ticket #460:
1934 * http://trac.pjsip.org/repos/ticket/460#comment:4
1935 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001936 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1937 pjsua_call_id call_id;
1938
Benny Prijonod8179652008-01-23 20:39:07 +00001939 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001940 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1941 }
1942}
1943
1944
Benny Prijono0bc99a92011-03-17 04:34:43 +00001945static pj_status_t audio_channel_update(pjsua_call_media *call_med,
1946 pj_pool_t *tmp_pool,
1947 const pjmedia_sdp_session *local_sdp,
1948 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001949{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001950 pjsua_call *call = call_med->call;
1951 pjmedia_stream_info the_si, *si = &the_si;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001952 pjmedia_port *media_port;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001953 unsigned strm_idx = call_med->idx;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001954 pj_status_t status;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001955
1956 status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
1957 local_sdp, remote_sdp, strm_idx);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001958 if (status != PJ_SUCCESS)
1959 return status;
1960
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001961 /* Check if no media is active */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001962 if (si->dir == PJMEDIA_DIR_NONE) {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001963 /* Call media state */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001964 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001965
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001966 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001967 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001968
Benny Prijonoc97608e2007-03-23 16:34:20 +00001969 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001970 pjmedia_transport_info tp_info;
1971
Benny Prijono224b4e22008-06-19 14:10:28 +00001972 /* Start/restart media transport */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001973 status = pjmedia_transport_media_start(call_med->tp,
1974 tmp_pool, local_sdp,
1975 remote_sdp, strm_idx);
Benny Prijonod8179652008-01-23 20:39:07 +00001976 if (status != PJ_SUCCESS)
1977 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001978
Benny Prijono0bc99a92011-03-17 04:34:43 +00001979 call_med->tp_st = PJSUA_MED_TP_RUNNING;
Benny Prijono224b4e22008-06-19 14:10:28 +00001980
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001981 /* Get remote SRTP usage policy */
1982 pjmedia_transport_info_init(&tp_info);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001983 pjmedia_transport_get_info(call_med->tp, &tp_info);
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001984 if (tp_info.specific_info_cnt > 0) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +00001985 unsigned i;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001986 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1987 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1988 {
1989 pjmedia_srtp_info *srtp_info =
1990 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1991
Benny Prijono0bc99a92011-03-17 04:34:43 +00001992 call_med->rem_srtp_use = srtp_info->peer_use;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001993 break;
1994 }
1995 }
1996 }
1997
Benny Prijonoc97608e2007-03-23 16:34:20 +00001998 /* Override ptime, if this option is specified. */
1999 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00002000 si->param->setting.frm_per_pkt = (pj_uint8_t)
2001 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
2002 if (si->param->setting.frm_per_pkt == 0)
2003 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002004 }
2005
2006 /* Disable VAD, if this option is specified. */
2007 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00002008 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002009 }
2010
2011
2012 /* Optionally, application may modify other stream settings here
2013 * (such as jitter buffer parameters, codec ptime, etc.)
2014 */
Benny Prijono91e567e2007-12-28 08:51:58 +00002015 si->jb_init = pjsua_var.media_cfg.jb_init;
2016 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
2017 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
2018 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002019
Benny Prijono8147f402007-11-21 14:50:07 +00002020 /* Set SSRC */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002021 si->ssrc = call_med->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00002022
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002023 /* Set RTP timestamp & sequence, normally these value are intialized
2024 * automatically when stream session created, but for some cases (e.g:
2025 * call reinvite, call update) timestamp and sequence need to be kept
2026 * contigue.
2027 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002028 si->rtp_ts = call_med->rtp_tx_ts;
2029 si->rtp_seq = call_med->rtp_tx_seq;
2030 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002031
Nanang Izzuddin5e39a2b2010-09-20 06:13:02 +00002032#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
2033 /* Enable/disable stream keep-alive and NAT hole punch. */
2034 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
2035#endif
2036
Benny Prijonoc97608e2007-03-23 16:34:20 +00002037 /* Create session based on session info. */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002038 status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,
2039 call_med->tp, NULL,
2040 &call_med->strm.a.stream);
2041 if (status != PJ_SUCCESS) {
2042 return status;
2043 }
2044
2045 /* Start stream */
2046 status = pjmedia_stream_start(call_med->strm.a.stream);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002047 if (status != PJ_SUCCESS) {
2048 return status;
2049 }
2050
2051 /* If DTMF callback is installed by application, install our
2052 * callback to the session.
2053 */
2054 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002055 pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
2056 &dtmf_callback,
2057 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00002058 }
2059
2060 /* Get the port interface of the first stream in the session.
2061 * We need the port interface to add to the conference bridge.
2062 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002063 pjmedia_stream_get_port(call_med->strm.a.stream, &media_port);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002064
Benny Prijonofc13bf62008-02-20 08:56:15 +00002065 /* Notify application about stream creation.
2066 * Note: application may modify media_port to point to different
2067 * media port
2068 */
2069 if (pjsua_var.ua_cfg.cb.on_stream_created) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002070 pjsua_var.ua_cfg.cb.on_stream_created(call->index,
2071 call_med->strm.a.stream,
2072 strm_idx, &media_port);
Benny Prijonofc13bf62008-02-20 08:56:15 +00002073 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002074
2075 /*
2076 * Add the call to conference bridge.
2077 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002078 {
2079 char tmp[PJSIP_MAX_URL_SIZE];
2080 pj_str_t port_name;
2081
2082 port_name.ptr = tmp;
2083 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
2084 call->inv->dlg->remote.info->uri,
2085 tmp, sizeof(tmp));
2086 if (port_name.slen < 1) {
2087 port_name = pj_str("call");
2088 }
Benny Prijono40d62b62009-08-12 17:53:47 +00002089 status = pjmedia_conf_add_port( pjsua_var.mconf,
2090 call->inv->pool_prov,
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002091 media_port,
2092 &port_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +00002093 (unsigned*)
2094 &call_med->strm.a.conf_slot);
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002095 if (status != PJ_SUCCESS) {
2096 return status;
2097 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002098 }
2099
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002100 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002101 call_med->dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002102
2103 /* Call media state */
2104 if (call->local_hold)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002105 call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
2106 else if (call_med->dir == PJMEDIA_DIR_DECODING)
2107 call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002108 else
Benny Prijono0bc99a92011-03-17 04:34:43 +00002109 call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002110 }
2111
2112 /* Print info. */
2113 {
2114 char info[80];
2115 int info_len = 0;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002116 int len;
2117 const char *dir;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002118
Benny Prijono0bc99a92011-03-17 04:34:43 +00002119 switch (si->dir) {
2120 case PJMEDIA_DIR_NONE:
2121 dir = "inactive";
2122 break;
2123 case PJMEDIA_DIR_ENCODING:
2124 dir = "sendonly";
2125 break;
2126 case PJMEDIA_DIR_DECODING:
2127 dir = "recvonly";
2128 break;
2129 case PJMEDIA_DIR_ENCODING_DECODING:
2130 dir = "sendrecv";
2131 break;
2132 default:
2133 dir = "unknown";
2134 break;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002135 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00002136 len = pj_ansi_sprintf( info+info_len,
2137 ", stream #%d: %.*s (%s)", strm_idx,
2138 (int)si->fmt.encoding_name.slen,
2139 si->fmt.encoding_name.ptr,
2140 dir);
2141 if (len > 0)
2142 info_len += len;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002143 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
2144 }
2145
2146 return PJ_SUCCESS;
2147}
2148
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002149
2150#if PJMEDIA_HAS_VIDEO
2151
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002152static pj_status_t video_channel_update(pjsua_call_media *call_med,
2153 pj_pool_t *tmp_pool,
2154 const pjmedia_sdp_session *local_sdp,
2155 const pjmedia_sdp_session *remote_sdp)
2156{
2157 pjsua_call *call = call_med->call;
2158 pjmedia_vid_stream_info the_si, *si = &the_si;
2159 pjmedia_port *media_port;
2160 unsigned strm_idx = call_med->idx;
2161 pj_status_t status;
2162
2163 status = pjmedia_vid_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
2164 local_sdp, remote_sdp, strm_idx);
2165 if (status != PJ_SUCCESS)
2166 return status;
2167
2168 /* Check if no media is active */
2169 if (si->dir == PJMEDIA_DIR_NONE) {
2170 /* Call media state */
2171 call_med->state = PJSUA_CALL_MEDIA_NONE;
2172
2173 /* Call media direction */
2174 call_med->dir = PJMEDIA_DIR_NONE;
2175
2176 } else {
2177 pjmedia_transport_info tp_info;
2178
2179 /* Start/restart media transport */
2180 status = pjmedia_transport_media_start(call_med->tp,
2181 tmp_pool, local_sdp,
2182 remote_sdp, strm_idx);
2183 if (status != PJ_SUCCESS)
2184 return status;
2185
2186 call_med->tp_st = PJSUA_MED_TP_RUNNING;
2187
2188 /* Get remote SRTP usage policy */
2189 pjmedia_transport_info_init(&tp_info);
2190 pjmedia_transport_get_info(call_med->tp, &tp_info);
2191 if (tp_info.specific_info_cnt > 0) {
2192 unsigned i;
2193 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
2194 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
2195 {
2196 pjmedia_srtp_info *srtp_info =
2197 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
2198
2199 call_med->rem_srtp_use = srtp_info->peer_use;
2200 break;
2201 }
2202 }
2203 }
2204
2205 /* Optionally, application may modify other stream settings here
2206 * (such as jitter buffer parameters, codec ptime, etc.)
2207 */
2208 si->jb_init = pjsua_var.media_cfg.jb_init;
2209 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
2210 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
2211 si->jb_max = pjsua_var.media_cfg.jb_max;
2212
2213 /* Set SSRC */
2214 si->ssrc = call_med->ssrc;
2215
2216 /* Set RTP timestamp & sequence, normally these value are intialized
2217 * automatically when stream session created, but for some cases (e.g:
2218 * call reinvite, call update) timestamp and sequence need to be kept
2219 * contigue.
2220 */
2221 si->rtp_ts = call_med->rtp_tx_ts;
2222 si->rtp_seq = call_med->rtp_tx_seq;
2223 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
2224
2225#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
2226 /* Enable/disable stream keep-alive and NAT hole punch. */
2227 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
2228#endif
2229
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002230 /* Try to get shared format ID between the capture device and
2231 * the encoder to avoid format conversion in the capture device.
2232 */
2233 if (si->dir & PJMEDIA_DIR_ENCODING) {
2234 pjmedia_vid_dev_info dev_info;
2235 pjmedia_vid_codec_info *codec_info = &si->codec_info;
2236 unsigned i, j;
2237
2238 status = pjmedia_vid_dev_get_info(pjsua_var.vcap_dev, &dev_info);
2239 if (status != PJ_SUCCESS)
2240 return status;
2241
2242 /* Find matched format ID */
2243 for (i = 0; i < codec_info->dec_fmt_id_cnt; ++i) {
2244 for (j = 0; j < dev_info.fmt_cnt; ++j) {
2245 if (codec_info->dec_fmt_id[i] ==
2246 (pjmedia_format_id)dev_info.fmt[j].id)
2247 {
2248 /* Apply the matched format ID to the codec */
2249 si->codec_param->dec_fmt.id = codec_info->dec_fmt_id[i];
2250 /* Force outer loop to break */
2251 i = codec_info->dec_fmt_id_cnt;
2252 break;
2253 }
2254 }
2255 }
2256 }
2257
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002258 /* Create session based on session info. */
2259 status = pjmedia_vid_stream_create(pjsua_var.med_endpt, NULL, si,
2260 call_med->tp, NULL,
2261 &call_med->strm.v.stream);
2262 if (status != PJ_SUCCESS)
2263 return status;
2264
2265 /* Start stream */
2266 status = pjmedia_vid_stream_start(call_med->strm.v.stream);
2267 if (status != PJ_SUCCESS)
2268 return status;
2269
2270 /* Setup decoding direction */
2271 if (si->dir & PJMEDIA_DIR_DECODING) {
2272 pjmedia_vid_port_param vport_param;
2273
2274 status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
2275 PJMEDIA_DIR_DECODING,
2276 &media_port);
2277 if (status != PJ_SUCCESS)
2278 return status;
2279
2280 status = pjmedia_vid_dev_default_param(
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002281 tmp_pool, pjsua_var.vrdr_dev,
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002282 &vport_param.vidparam);
2283 if (status != PJ_SUCCESS)
2284 return status;
2285
2286 pjmedia_format_copy(&vport_param.vidparam.fmt,
2287 &media_port->info.fmt);
2288
2289 vport_param.vidparam.dir = PJMEDIA_DIR_RENDER;
2290 vport_param.active = PJ_TRUE;
2291
2292 /* Create video renderer */
2293 status = pjmedia_vid_port_create(tmp_pool, &vport_param,
2294 &call_med->strm.v.renderer);
2295 if (status != PJ_SUCCESS)
2296 return status;
2297
2298 /* Connect the video renderer to media_port */
2299 status = pjmedia_vid_port_connect(call_med->strm.v.renderer,
2300 media_port, PJ_FALSE);
2301 if (status != PJ_SUCCESS)
2302 return status;
2303
2304 /* Start the video renderer */
2305 status = pjmedia_vid_port_start(call_med->strm.v.renderer);
2306 if (status != PJ_SUCCESS)
2307 return status;
2308 }
2309
2310 /* Setup encoding direction */
Nanang Izzuddina6414292011-04-08 04:26:18 +00002311 if (si->dir & PJMEDIA_DIR_ENCODING && !call->local_hold) {
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002312 pjmedia_vid_port_param vport_param;
2313
2314 status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
2315 PJMEDIA_DIR_ENCODING,
2316 &media_port);
2317 if (status != PJ_SUCCESS)
2318 return status;
2319
2320 status = pjmedia_vid_dev_default_param(
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002321 tmp_pool, pjsua_var.vcap_dev,
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002322 &vport_param.vidparam);
2323 if (status != PJ_SUCCESS)
2324 return status;
2325
2326 pjmedia_format_copy(&vport_param.vidparam.fmt,
2327 &media_port->info.fmt);
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002328 vport_param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
2329 vport_param.active = PJ_TRUE;
2330
2331 /* Create video capturer */
2332 status = pjmedia_vid_port_create(tmp_pool, &vport_param,
2333 &call_med->strm.v.capturer);
2334 if (status != PJ_SUCCESS)
2335 return status;
2336
2337 /* Connect the video capturer to media_port */
2338 status = pjmedia_vid_port_connect(call_med->strm.v.capturer,
2339 media_port, PJ_FALSE);
2340 if (status != PJ_SUCCESS)
2341 return status;
2342
2343 /* Start the video capturer */
2344 status = pjmedia_vid_port_start(call_med->strm.v.capturer);
2345 if (status != PJ_SUCCESS)
2346 return status;
2347 }
2348
2349 /* Call media direction */
2350 call_med->dir = si->dir;
2351
2352 /* Call media state */
2353 if (call->local_hold)
2354 call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
2355 else if (call_med->dir == PJMEDIA_DIR_DECODING)
2356 call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
2357 else
2358 call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
2359 }
2360
2361 /* Print info. */
2362 {
2363 char info[80];
2364 int info_len = 0;
2365 int len;
2366 const char *dir;
2367
2368 switch (si->dir) {
2369 case PJMEDIA_DIR_NONE:
2370 dir = "inactive";
2371 break;
2372 case PJMEDIA_DIR_ENCODING:
2373 dir = "sendonly";
2374 break;
2375 case PJMEDIA_DIR_DECODING:
2376 dir = "recvonly";
2377 break;
2378 case PJMEDIA_DIR_ENCODING_DECODING:
2379 dir = "sendrecv";
2380 break;
2381 default:
2382 dir = "unknown";
2383 break;
2384 }
2385 len = pj_ansi_sprintf( info+info_len,
2386 ", stream #%d: %.*s (%s)", strm_idx,
2387 (int)si->codec_info.encoding_name.slen,
2388 si->codec_info.encoding_name.ptr,
2389 dir);
2390 if (len > 0)
2391 info_len += len;
2392 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
2393 }
2394
2395 return PJ_SUCCESS;
2396}
2397
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002398#endif
2399
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002400
Benny Prijono0bc99a92011-03-17 04:34:43 +00002401pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
2402 const pjmedia_sdp_session *local_sdp,
2403 const pjmedia_sdp_session *remote_sdp)
2404{
2405 pjsua_call *call = &pjsua_var.calls[call_id];
2406 pj_pool_t *tmp_pool = call->inv->pool_prov;
2407 unsigned mi;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002408 pj_status_t status = PJ_SUCCESS;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002409
2410 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
2411 return PJ_EBUSY;
2412
2413 /* Destroy existing media session, if any. */
2414 stop_media_session(call->index);
2415
2416 /* Reset audio_idx first */
2417 call->audio_idx = -1;
2418
2419 /* Process each media stream */
2420 for (mi=0; mi < call->med_cnt; ++mi) {
2421 pjsua_call_media *call_med = &call->media[mi];
2422
2423 if (mi > local_sdp->media_count ||
2424 mi > remote_sdp->media_count)
2425 {
2426 /* Something is wrong */
2427 PJ_LOG(1,(THIS_FILE, "Error updating media for call %d: "
2428 "invalid media index %d in SDP", call_id, mi));
2429 return PJMEDIA_SDP_EINSDP;
2430 }
2431
2432 switch (call_med->type) {
2433 case PJMEDIA_TYPE_AUDIO:
2434 status = audio_channel_update(call_med, tmp_pool,
2435 local_sdp, remote_sdp);
2436 if (call->audio_idx==-1 && status==PJ_SUCCESS &&
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002437 call_med->strm.a.stream)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002438 {
2439 call->audio_idx = mi;
2440 }
2441 break;
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002442#if PJMEDIA_HAS_VIDEO
Benny Prijono0bc99a92011-03-17 04:34:43 +00002443 case PJMEDIA_TYPE_VIDEO:
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002444 status = video_channel_update(call_med, tmp_pool,
2445 local_sdp, remote_sdp);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002446 break;
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002447#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002448 default:
2449 break;
2450 }
2451
2452 if (status != PJ_SUCCESS) {
2453 PJ_PERROR(1,(THIS_FILE, status, "Error updating media call%02:%d",
2454 call_id, mi));
2455 }
2456 }
2457
2458 return PJ_SUCCESS;
2459}
2460
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002461/*
2462 * Get maxinum number of conference ports.
2463 */
2464PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
2465{
2466 return pjsua_var.media_cfg.max_media_ports;
2467}
2468
2469
2470/*
2471 * Get current number of active ports in the bridge.
2472 */
2473PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
2474{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002475 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002476 unsigned count = PJ_ARRAY_SIZE(ports);
2477 pj_status_t status;
2478
2479 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
2480 if (status != PJ_SUCCESS)
2481 count = 0;
2482
2483 return count;
2484}
2485
2486
2487/*
2488 * Enumerate all conference ports.
2489 */
2490PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
2491 unsigned *count)
2492{
2493 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
2494}
2495
2496
2497/*
2498 * Get information about the specified conference port
2499 */
2500PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
2501 pjsua_conf_port_info *info)
2502{
2503 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00002504 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002505 pj_status_t status;
2506
2507 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
2508 if (status != PJ_SUCCESS)
2509 return status;
2510
Benny Prijonoac623b32006-07-03 15:19:31 +00002511 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002512 info->slot_id = id;
2513 info->name = cinfo.name;
2514 info->clock_rate = cinfo.clock_rate;
2515 info->channel_count = cinfo.channel_count;
2516 info->samples_per_frame = cinfo.samples_per_frame;
2517 info->bits_per_sample = cinfo.bits_per_sample;
2518
2519 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00002520 info->listener_cnt = cinfo.listener_cnt;
2521 for (i=0; i<cinfo.listener_cnt; ++i) {
2522 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002523 }
2524
2525 return PJ_SUCCESS;
2526}
2527
2528
2529/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00002530 * Add arbitrary media port to PJSUA's conference bridge.
2531 */
2532PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
2533 pjmedia_port *port,
2534 pjsua_conf_port_id *p_id)
2535{
2536 pj_status_t status;
2537
2538 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
2539 port, NULL, (unsigned*)p_id);
2540 if (status != PJ_SUCCESS) {
2541 if (p_id)
2542 *p_id = PJSUA_INVALID_ID;
2543 }
2544
2545 return status;
2546}
2547
2548
2549/*
2550 * Remove arbitrary slot from the conference bridge.
2551 */
2552PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
2553{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002554 pj_status_t status;
2555
2556 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
2557 check_snd_dev_idle();
2558
2559 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00002560}
2561
2562
2563/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002564 * Establish unidirectional media flow from souce to sink.
2565 */
2566PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
2567 pjsua_conf_port_id sink)
2568{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002569 /* If sound device idle timer is active, cancel it first. */
Benny Prijono0f711b42009-05-06 19:08:43 +00002570 PJSUA_LOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002571 if (pjsua_var.snd_idle_timer.id) {
2572 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
2573 pjsua_var.snd_idle_timer.id = PJ_FALSE;
2574 }
Benny Prijono0f711b42009-05-06 19:08:43 +00002575 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002576
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002577
Benny Prijonof798e502009-03-09 13:08:16 +00002578 /* For audio switchboard (i.e. APS-Direct):
2579 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002580 * (format, clock rate, channel count) must match to peer's.
2581 * Note that sound device can be reopened only if it doesn't have
2582 * any connection.
2583 */
Benny Prijonof798e502009-03-09 13:08:16 +00002584 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002585 pjmedia_conf_port_info port0_info;
2586 pjmedia_conf_port_info peer_info;
2587 unsigned peer_id;
2588 pj_bool_t need_reopen = PJ_FALSE;
2589 pj_status_t status;
2590
2591 peer_id = (source!=0)? source : sink;
2592 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
2593 &peer_info);
2594 pj_assert(status == PJ_SUCCESS);
2595
2596 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
2597 pj_assert(status == PJ_SUCCESS);
2598
2599 /* Check if sound device is instantiated. */
2600 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2601 !pjsua_var.no_snd);
2602
2603 /* Check if sound device need to reopen because it needs to modify
2604 * settings to match its peer. Sound device must be idle in this case
2605 * though.
2606 */
2607 if (!need_reopen &&
2608 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
2609 {
2610 need_reopen = (peer_info.format.id != port0_info.format.id ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002611 peer_info.format.det.aud.avg_bps !=
2612 port0_info.format.det.aud.avg_bps ||
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002613 peer_info.clock_rate != port0_info.clock_rate ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002614 peer_info.channel_count!=port0_info.channel_count);
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002615 }
2616
2617 if (need_reopen) {
Benny Prijonod65f78c2009-06-03 18:59:37 +00002618 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
2619 pjmedia_aud_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002620
Benny Prijonod65f78c2009-06-03 18:59:37 +00002621 /* Create parameter based on peer info */
2622 status = create_aud_param(&param, pjsua_var.cap_dev,
2623 pjsua_var.play_dev,
2624 peer_info.clock_rate,
2625 peer_info.channel_count,
2626 peer_info.samples_per_frame,
2627 peer_info.bits_per_sample);
2628 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002629 pjsua_perror(THIS_FILE, "Error opening sound device",
2630 status);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002631 return status;
2632 }
Benny Prijonof798e502009-03-09 13:08:16 +00002633
Benny Prijonod65f78c2009-06-03 18:59:37 +00002634 /* And peer format */
2635 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
2636 param.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
2637 param.ext_fmt = peer_info.format;
2638 }
Benny Prijono10454dc2009-02-21 14:21:59 +00002639
Benny Prijonod65f78c2009-06-03 18:59:37 +00002640 status = open_snd_dev(&param);
2641 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002642 pjsua_perror(THIS_FILE, "Error opening sound device",
2643 status);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002644 return status;
2645 }
2646 } else {
2647 /* Null-audio */
Benny Prijonoc45d9512010-12-10 11:04:30 +00002648 status = pjsua_set_snd_dev(pjsua_var.cap_dev,
2649 pjsua_var.play_dev);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002650 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002651 pjsua_perror(THIS_FILE, "Error opening sound device",
2652 status);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002653 return status;
2654 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002655 }
2656 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002657
Benny Prijonof798e502009-03-09 13:08:16 +00002658 } else {
2659 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002660
Benny Prijonof798e502009-03-09 13:08:16 +00002661 /* Create sound port if none is instantiated */
2662 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2663 !pjsua_var.no_snd)
2664 {
2665 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002666
Benny Prijonof798e502009-03-09 13:08:16 +00002667 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
2668 if (status != PJ_SUCCESS) {
2669 pjsua_perror(THIS_FILE, "Error opening sound device", status);
2670 return status;
2671 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002672 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002673
Benny Prijonof798e502009-03-09 13:08:16 +00002674 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002675
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002676 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
2677}
2678
2679
2680/*
2681 * Disconnect media flow from the source to destination port.
2682 */
2683PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
2684 pjsua_conf_port_id sink)
2685{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002686 pj_status_t status;
2687
2688 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002689 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002690
2691 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002692}
2693
2694
Benny Prijono6dd967c2006-12-26 02:27:14 +00002695/*
2696 * Adjust the signal level to be transmitted from the bridge to the
2697 * specified port by making it louder or quieter.
2698 */
2699PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
2700 float level)
2701{
2702 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
2703 (int)((level-1) * 128));
2704}
2705
2706/*
2707 * Adjust the signal level to be received from the specified port (to
2708 * the bridge) by making it louder or quieter.
2709 */
2710PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
2711 float level)
2712{
2713 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
2714 (int)((level-1) * 128));
2715}
2716
2717
2718/*
2719 * Get last signal level transmitted to or received from the specified port.
2720 */
2721PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
2722 unsigned *tx_level,
2723 unsigned *rx_level)
2724{
2725 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
2726 tx_level, rx_level);
2727}
2728
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002729/*****************************************************************************
2730 * File player.
2731 */
2732
Benny Prijonod5696da2007-07-17 16:25:45 +00002733static char* get_basename(const char *path, unsigned len)
2734{
2735 char *p = ((char*)path) + len;
2736
2737 if (len==0)
2738 return p;
2739
Benny Prijono1f61a8f2007-08-16 10:11:44 +00002740 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00002741
2742 return (p==path) ? p : p+1;
2743}
2744
2745
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002746/*
2747 * Create a file player, and automatically connect this player to
2748 * the conference bridge.
2749 */
2750PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
2751 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002752 pjsua_player_id *p_id)
2753{
2754 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002755 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00002756 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002757 pjmedia_port *port;
2758 pj_status_t status;
2759
2760 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2761 return PJ_ETOOMANY;
2762
2763 PJSUA_LOCK();
2764
2765 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2766 if (pjsua_var.player[file_id].port == NULL)
2767 break;
2768 }
2769
2770 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2771 /* This is unexpected */
2772 PJSUA_UNLOCK();
2773 pj_assert(0);
2774 return PJ_EBUG;
2775 }
2776
2777 pj_memcpy(path, filename->ptr, filename->slen);
2778 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00002779
2780 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2781 if (!pool) {
2782 PJSUA_UNLOCK();
2783 return PJ_ENOMEM;
2784 }
2785
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00002786 status = pjmedia_wav_player_port_create(
2787 pool, path,
2788 pjsua_var.mconf_cfg.samples_per_frame *
Benny Prijonoc45d9512010-12-10 11:04:30 +00002789 1000 / pjsua_var.media_cfg.channel_count /
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00002790 pjsua_var.media_cfg.clock_rate,
2791 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002792 if (status != PJ_SUCCESS) {
2793 PJSUA_UNLOCK();
2794 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002795 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002796 return status;
2797 }
2798
Benny Prijono5297af92008-03-18 13:40:40 +00002799 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002800 port, filename, &slot);
2801 if (status != PJ_SUCCESS) {
2802 pjmedia_port_destroy(port);
2803 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00002804 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
2805 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002806 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002807 return status;
2808 }
2809
Benny Prijonoa66c3312007-01-21 23:12:40 +00002810 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00002811 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002812 pjsua_var.player[file_id].port = port;
2813 pjsua_var.player[file_id].slot = slot;
2814
2815 if (p_id) *p_id = file_id;
2816
2817 ++pjsua_var.player_cnt;
2818
2819 PJSUA_UNLOCK();
2820 return PJ_SUCCESS;
2821}
2822
2823
2824/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00002825 * Create a file playlist media port, and automatically add the port
2826 * to the conference bridge.
2827 */
2828PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
2829 unsigned file_count,
2830 const pj_str_t *label,
2831 unsigned options,
2832 pjsua_player_id *p_id)
2833{
2834 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00002835 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002836 pjmedia_port *port;
2837 pj_status_t status;
2838
2839 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2840 return PJ_ETOOMANY;
2841
2842 PJSUA_LOCK();
2843
2844 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2845 if (pjsua_var.player[file_id].port == NULL)
2846 break;
2847 }
2848
2849 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2850 /* This is unexpected */
2851 PJSUA_UNLOCK();
2852 pj_assert(0);
2853 return PJ_EBUG;
2854 }
2855
2856
2857 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
2858 pjsua_var.media_cfg.clock_rate;
2859
Benny Prijonod5696da2007-07-17 16:25:45 +00002860 pool = pjsua_pool_create("playlist", 1000, 1000);
2861 if (!pool) {
2862 PJSUA_UNLOCK();
2863 return PJ_ENOMEM;
2864 }
2865
2866 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002867 file_names, file_count,
2868 ptime, options, 0, &port);
2869 if (status != PJ_SUCCESS) {
2870 PJSUA_UNLOCK();
2871 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002872 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002873 return status;
2874 }
2875
Benny Prijonod5696da2007-07-17 16:25:45 +00002876 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002877 port, &port->info.name, &slot);
2878 if (status != PJ_SUCCESS) {
2879 pjmedia_port_destroy(port);
2880 PJSUA_UNLOCK();
2881 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002882 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002883 return status;
2884 }
2885
2886 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00002887 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002888 pjsua_var.player[file_id].port = port;
2889 pjsua_var.player[file_id].slot = slot;
2890
2891 if (p_id) *p_id = file_id;
2892
2893 ++pjsua_var.player_cnt;
2894
2895 PJSUA_UNLOCK();
2896 return PJ_SUCCESS;
2897
2898}
2899
2900
2901/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002902 * Get conference port ID associated with player.
2903 */
2904PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
2905{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002906 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002907 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2908
2909 return pjsua_var.player[id].slot;
2910}
2911
Benny Prijono469b1522006-12-26 03:05:17 +00002912/*
2913 * Get the media port for the player.
2914 */
Benny Prijonobe41d862008-01-18 13:24:28 +00002915PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00002916 pjmedia_port **p_port)
2917{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002918 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002919 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2920 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2921
2922 *p_port = pjsua_var.player[id].port;
2923
2924 return PJ_SUCCESS;
2925}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002926
2927/*
2928 * Set playback position.
2929 */
2930PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
2931 pj_uint32_t samples)
2932{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002933 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002934 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002935 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002936
2937 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
2938}
2939
2940
2941/*
2942 * Close the file, remove the player from the bridge, and free
2943 * resources associated with the file player.
2944 */
2945PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
2946{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002947 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002948 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2949
2950 PJSUA_LOCK();
2951
2952 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002953 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002954 pjmedia_port_destroy(pjsua_var.player[id].port);
2955 pjsua_var.player[id].port = NULL;
2956 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002957 pj_pool_release(pjsua_var.player[id].pool);
2958 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002959 pjsua_var.player_cnt--;
2960 }
2961
2962 PJSUA_UNLOCK();
2963
2964 return PJ_SUCCESS;
2965}
2966
2967
2968/*****************************************************************************
2969 * File recorder.
2970 */
2971
2972/*
2973 * Create a file recorder, and automatically connect this recorder to
2974 * the conference bridge.
2975 */
2976PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00002977 unsigned enc_type,
2978 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002979 pj_ssize_t max_size,
2980 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002981 pjsua_recorder_id *p_id)
2982{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002983 enum Format
2984 {
2985 FMT_UNKNOWN,
2986 FMT_WAV,
2987 FMT_MP3,
2988 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002989 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002990 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002991 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00002992 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00002993 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002994 pjmedia_port *port;
2995 pj_status_t status;
2996
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002997 /* Filename must present */
2998 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
2999
Benny Prijono00cae612006-07-31 15:19:36 +00003000 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003001 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00003002
Benny Prijono8f310522006-10-20 11:08:49 +00003003 /* Don't support encoding type at present */
3004 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00003005
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003006 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
3007 return PJ_ETOOMANY;
3008
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003009 /* Determine the file format */
3010 ext.ptr = filename->ptr + filename->slen - 4;
3011 ext.slen = 4;
3012
3013 if (pj_stricmp2(&ext, ".wav") == 0)
3014 file_format = FMT_WAV;
3015 else if (pj_stricmp2(&ext, ".mp3") == 0)
3016 file_format = FMT_MP3;
3017 else {
3018 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
3019 "determine file format for %.*s",
3020 (int)filename->slen, filename->ptr));
3021 return PJ_ENOTSUP;
3022 }
3023
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003024 PJSUA_LOCK();
3025
3026 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
3027 if (pjsua_var.recorder[file_id].port == NULL)
3028 break;
3029 }
3030
3031 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
3032 /* This is unexpected */
3033 PJSUA_UNLOCK();
3034 pj_assert(0);
3035 return PJ_EBUG;
3036 }
3037
3038 pj_memcpy(path, filename->ptr, filename->slen);
3039 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003040
Benny Prijonod5696da2007-07-17 16:25:45 +00003041 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
3042 if (!pool) {
3043 PJSUA_UNLOCK();
3044 return PJ_ENOMEM;
3045 }
3046
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003047 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00003048 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003049 pjsua_var.media_cfg.clock_rate,
3050 pjsua_var.mconf_cfg.channel_count,
3051 pjsua_var.mconf_cfg.samples_per_frame,
3052 pjsua_var.mconf_cfg.bits_per_sample,
3053 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003054 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00003055 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003056 port = NULL;
3057 status = PJ_ENOTSUP;
3058 }
3059
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003060 if (status != PJ_SUCCESS) {
3061 PJSUA_UNLOCK();
3062 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00003063 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003064 return status;
3065 }
3066
Benny Prijonod5696da2007-07-17 16:25:45 +00003067 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003068 port, filename, &slot);
3069 if (status != PJ_SUCCESS) {
3070 pjmedia_port_destroy(port);
3071 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00003072 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003073 return status;
3074 }
3075
3076 pjsua_var.recorder[file_id].port = port;
3077 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00003078 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003079
3080 if (p_id) *p_id = file_id;
3081
3082 ++pjsua_var.rec_cnt;
3083
3084 PJSUA_UNLOCK();
3085 return PJ_SUCCESS;
3086}
3087
3088
3089/*
3090 * Get conference port associated with recorder.
3091 */
3092PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
3093{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003094 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3095 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003096 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3097
3098 return pjsua_var.recorder[id].slot;
3099}
3100
Benny Prijono469b1522006-12-26 03:05:17 +00003101/*
3102 * Get the media port for the recorder.
3103 */
3104PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
3105 pjmedia_port **p_port)
3106{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003107 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3108 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00003109 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3110 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
3111
3112 *p_port = pjsua_var.recorder[id].port;
3113 return PJ_SUCCESS;
3114}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003115
3116/*
3117 * Destroy recorder (this will complete recording).
3118 */
3119PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
3120{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003121 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3122 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003123 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3124
3125 PJSUA_LOCK();
3126
3127 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00003128 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003129 pjmedia_port_destroy(pjsua_var.recorder[id].port);
3130 pjsua_var.recorder[id].port = NULL;
3131 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00003132 pj_pool_release(pjsua_var.recorder[id].pool);
3133 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003134 pjsua_var.rec_cnt--;
3135 }
3136
3137 PJSUA_UNLOCK();
3138
3139 return PJ_SUCCESS;
3140}
3141
3142
3143/*****************************************************************************
3144 * Sound devices.
3145 */
3146
3147/*
3148 * Enum sound devices.
3149 */
Benny Prijonof798e502009-03-09 13:08:16 +00003150
3151PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003152 unsigned *count)
3153{
3154 unsigned i, dev_count;
3155
Benny Prijono10454dc2009-02-21 14:21:59 +00003156 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003157
3158 if (dev_count > *count) dev_count = *count;
3159
3160 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00003161 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003162
Benny Prijono10454dc2009-02-21 14:21:59 +00003163 status = pjmedia_aud_dev_get_info(i, &info[i]);
3164 if (status != PJ_SUCCESS)
3165 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003166 }
3167
3168 *count = dev_count;
3169
3170 return PJ_SUCCESS;
3171}
Benny Prijonof798e502009-03-09 13:08:16 +00003172
3173
Benny Prijono10454dc2009-02-21 14:21:59 +00003174PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
3175 unsigned *count)
3176{
3177 unsigned i, dev_count;
3178
3179 dev_count = pjmedia_aud_dev_count();
3180
3181 if (dev_count > *count) dev_count = *count;
3182 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
3183
3184 for (i=0; i<dev_count; ++i) {
3185 pjmedia_aud_dev_info ai;
3186 pj_status_t status;
3187
3188 status = pjmedia_aud_dev_get_info(i, &ai);
3189 if (status != PJ_SUCCESS)
3190 return status;
3191
3192 strncpy(info[i].name, ai.name, sizeof(info[i].name));
3193 info[i].name[sizeof(info[i].name)-1] = '\0';
3194 info[i].input_count = ai.input_count;
3195 info[i].output_count = ai.output_count;
3196 info[i].default_samples_per_sec = ai.default_samples_per_sec;
3197 }
3198
3199 *count = dev_count;
3200
3201 return PJ_SUCCESS;
3202}
Benny Prijono10454dc2009-02-21 14:21:59 +00003203
Benny Prijonof798e502009-03-09 13:08:16 +00003204/* Create audio device parameter to open the device */
3205static pj_status_t create_aud_param(pjmedia_aud_param *param,
3206 pjmedia_aud_dev_index capture_dev,
3207 pjmedia_aud_dev_index playback_dev,
3208 unsigned clock_rate,
3209 unsigned channel_count,
3210 unsigned samples_per_frame,
3211 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003212{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003213 pj_status_t status;
3214
Benny Prijono96e74f32009-02-22 12:00:12 +00003215 /* Normalize device ID with new convention about default device ID */
3216 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
3217 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
3218
Benny Prijono10454dc2009-02-21 14:21:59 +00003219 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00003220 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00003221 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00003222 pjsua_perror(THIS_FILE, "Error retrieving default audio "
3223 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00003224 return status;
3225 }
Benny Prijonof798e502009-03-09 13:08:16 +00003226 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
3227 param->rec_id = capture_dev;
3228 param->play_id = playback_dev;
3229 param->clock_rate = clock_rate;
3230 param->channel_count = channel_count;
3231 param->samples_per_frame = samples_per_frame;
3232 param->bits_per_sample = bits_per_sample;
3233
3234 /* Update the setting with user preference */
3235#define update_param(cap, field) \
3236 if (pjsua_var.aud_param.flags & cap) { \
3237 param->flags |= cap; \
3238 param->field = pjsua_var.aud_param.field; \
3239 }
3240 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3241 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3242 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3243 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3244#undef update_param
3245
Benny Prijono10454dc2009-02-21 14:21:59 +00003246 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00003247 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
3248 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
3249 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
3250 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
3251
Benny Prijono10454dc2009-02-21 14:21:59 +00003252 /* EC settings */
3253 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00003254 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
3255 param->ec_enabled = PJ_TRUE;
3256 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00003257 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00003258 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00003259 }
3260
Benny Prijonof798e502009-03-09 13:08:16 +00003261 return PJ_SUCCESS;
3262}
Benny Prijono26056d82006-10-11 16:03:41 +00003263
Benny Prijonof798e502009-03-09 13:08:16 +00003264/* Internal: the first time the audio device is opened (during app
3265 * startup), retrieve the audio settings such as volume level
3266 * so that aud_get_settings() will work.
3267 */
3268static pj_status_t update_initial_aud_param()
3269{
3270 pjmedia_aud_stream *strm;
3271 pjmedia_aud_param param;
3272 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00003273
Benny Prijonof798e502009-03-09 13:08:16 +00003274 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00003275
Benny Prijonof798e502009-03-09 13:08:16 +00003276 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00003277
Benny Prijonof798e502009-03-09 13:08:16 +00003278 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003279 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00003280 pjsua_perror(THIS_FILE, "Error audio stream "
3281 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003282 return status;
3283 }
3284
Benny Prijonof798e502009-03-09 13:08:16 +00003285#define update_saved_param(cap, field) \
3286 if (param.flags & cap) { \
3287 pjsua_var.aud_param.flags |= cap; \
3288 pjsua_var.aud_param.field = param.field; \
3289 }
3290
3291 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3292 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3293 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3294 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3295#undef update_saved_param
3296
3297 return PJ_SUCCESS;
3298}
3299
3300/* Get format name */
3301static const char *get_fmt_name(pj_uint32_t id)
3302{
3303 static char name[8];
3304
3305 if (id == PJMEDIA_FORMAT_L16)
3306 return "PCM";
3307 pj_memcpy(name, &id, 4);
3308 name[4] = '\0';
3309 return name;
3310}
3311
3312/* Open sound device with the setting. */
3313static pj_status_t open_snd_dev(pjmedia_aud_param *param)
3314{
3315 pjmedia_port *conf_port;
3316 pj_status_t status;
3317
3318 PJ_ASSERT_RETURN(param, PJ_EINVAL);
3319
3320 /* Check if NULL sound device is used */
3321 if (NULL_SND_DEV_ID==param->rec_id || NULL_SND_DEV_ID==param->play_id) {
3322 return pjsua_set_null_snd_dev();
3323 }
3324
3325 /* Close existing sound port */
3326 close_snd_dev();
3327
3328 /* Create memory pool for sound device. */
3329 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3330 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3331
3332
3333 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
3334 get_fmt_name(param->ext_fmt.id),
3335 param->clock_rate, param->channel_count,
3336 param->samples_per_frame / param->channel_count * 1000 /
3337 param->clock_rate));
3338
3339 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
3340 param, &pjsua_var.snd_port);
3341 if (status != PJ_SUCCESS)
3342 return status;
3343
3344 /* Get the port0 of the conference bridge. */
3345 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3346 pj_assert(conf_port != NULL);
3347
3348 /* For conference bridge, resample if necessary if the bridge's
3349 * clock rate is different than the sound device's clock rate.
3350 */
3351 if (!pjsua_var.is_mswitch &&
3352 param->ext_fmt.id == PJMEDIA_FORMAT_PCM &&
Benny Prijonoc45d9512010-12-10 11:04:30 +00003353 PJMEDIA_PIA_SRATE(&conf_port->info) != param->clock_rate)
Benny Prijonof798e502009-03-09 13:08:16 +00003354 {
3355 pjmedia_port *resample_port;
3356 unsigned resample_opt = 0;
3357
3358 if (pjsua_var.media_cfg.quality >= 3 &&
3359 pjsua_var.media_cfg.quality <= 4)
3360 {
3361 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
3362 }
3363 else if (pjsua_var.media_cfg.quality < 3) {
3364 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
3365 }
3366
3367 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
3368 conf_port,
3369 param->clock_rate,
3370 resample_opt,
3371 &resample_port);
3372 if (status != PJ_SUCCESS) {
3373 char errmsg[PJ_ERR_MSG_SIZE];
3374 pj_strerror(status, errmsg, sizeof(errmsg));
3375 PJ_LOG(4, (THIS_FILE,
3376 "Error creating resample port: %s",
3377 errmsg));
3378 close_snd_dev();
3379 return status;
3380 }
3381
3382 conf_port = resample_port;
3383 }
3384
3385 /* Otherwise for audio switchboard, the switch's port0 setting is
3386 * derived from the sound device setting, so update the setting.
3387 */
3388 if (pjsua_var.is_mswitch) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00003389 pj_memcpy(&conf_port->info.fmt, &param->ext_fmt,
3390 sizeof(conf_port->info.fmt));
3391 conf_port->info.fmt.det.aud.clock_rate = param->clock_rate;
3392 conf_port->info.fmt.det.aud.frame_time_usec = param->samples_per_frame*
3393 1000000 /
3394 param->clock_rate;
3395 conf_port->info.fmt.det.aud.channel_count = param->channel_count;
3396 conf_port->info.fmt.det.aud.bits_per_sample = 16;
Benny Prijonof798e502009-03-09 13:08:16 +00003397 }
3398
Benny Prijonoc45d9512010-12-10 11:04:30 +00003399
Benny Prijonof798e502009-03-09 13:08:16 +00003400 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00003401 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
3402 conf_port );
3403 if (status != PJ_SUCCESS) {
3404 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
3405 "sound device", status);
3406 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3407 pjsua_var.snd_port = NULL;
3408 return status;
3409 }
3410
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003411 /* Save the device IDs */
Benny Prijonof798e502009-03-09 13:08:16 +00003412 pjsua_var.cap_dev = param->rec_id;
3413 pjsua_var.play_dev = param->play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003414
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003415 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00003416 {
3417 pjmedia_aud_dev_info rec_info;
3418 pjmedia_aud_stream *strm;
3419 pjmedia_aud_param si;
3420 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003421
Benny Prijonof798e502009-03-09 13:08:16 +00003422 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3423 status = pjmedia_aud_stream_get_param(strm, &si);
3424 if (status == PJ_SUCCESS)
3425 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00003426
Benny Prijonof798e502009-03-09 13:08:16 +00003427 if (status==PJ_SUCCESS) {
3428 if (param->clock_rate != pjsua_var.media_cfg.clock_rate) {
3429 char tmp_buf[128];
3430 int tmp_buf_len = sizeof(tmp_buf);
3431
3432 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
3433 "%s (%dKHz)",
3434 rec_info.name,
3435 param->clock_rate/1000);
3436 pj_strset(&tmp, tmp_buf, tmp_buf_len);
3437 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
3438 } else {
3439 pjmedia_conf_set_port0_name(pjsua_var.mconf,
3440 pj_cstr(&tmp, rec_info.name));
3441 }
3442 }
3443
3444 /* Any error is not major, let it through */
3445 status = PJ_SUCCESS;
3446 };
3447
3448 /* If this is the first time the audio device is open, retrieve some
3449 * settings from the device (such as volume settings) so that the
3450 * pjsua_snd_get_setting() work.
3451 */
3452 if (pjsua_var.aud_open_cnt == 0) {
3453 update_initial_aud_param();
3454 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00003455 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003456
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003457 return PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00003458}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003459
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003460
Benny Prijonof798e502009-03-09 13:08:16 +00003461/* Close existing sound device */
3462static void close_snd_dev(void)
3463{
3464 /* Close sound device */
3465 if (pjsua_var.snd_port) {
3466 pjmedia_aud_dev_info cap_info, play_info;
3467 pjmedia_aud_stream *strm;
3468 pjmedia_aud_param param;
3469
3470 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3471 pjmedia_aud_stream_get_param(strm, &param);
3472
3473 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
3474 cap_info.name[0] = '\0';
3475 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
3476 play_info.name[0] = '\0';
3477
3478 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
3479 "%s sound capture device",
3480 play_info.name, cap_info.name));
3481
3482 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
3483 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3484 pjsua_var.snd_port = NULL;
3485 }
3486
3487 /* Close null sound device */
3488 if (pjsua_var.null_snd) {
3489 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
3490 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
3491 pjsua_var.null_snd = NULL;
3492 }
3493
3494 if (pjsua_var.snd_pool)
3495 pj_pool_release(pjsua_var.snd_pool);
3496 pjsua_var.snd_pool = NULL;
3497}
3498
3499
3500/*
3501 * Select or change sound device. Application may call this function at
3502 * any time to replace current sound device.
3503 */
3504PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
3505 int playback_dev)
3506{
3507 unsigned alt_cr_cnt = 1;
3508 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
3509 unsigned i;
3510 pj_status_t status = -1;
3511
Benny Prijono23ea21a2009-06-03 12:43:06 +00003512 /* Null-sound */
3513 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
3514 return pjsua_set_null_snd_dev();
3515 }
3516
Benny Prijonof798e502009-03-09 13:08:16 +00003517 /* Set default clock rate */
3518 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
3519 if (alt_cr[0] == 0)
3520 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
3521
3522 /* Allow retrying of different clock rate if we're using conference
3523 * bridge (meaning audio format is always PCM), otherwise lock on
3524 * to one clock rate.
3525 */
3526 if (pjsua_var.is_mswitch) {
3527 alt_cr_cnt = 1;
3528 } else {
3529 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
3530 }
3531
3532 /* Attempts to open the sound device with different clock rates */
3533 for (i=0; i<alt_cr_cnt; ++i) {
3534 pjmedia_aud_param param;
3535 unsigned samples_per_frame;
3536
3537 /* Create the default audio param */
3538 samples_per_frame = alt_cr[i] *
3539 pjsua_var.media_cfg.audio_frame_ptime *
3540 pjsua_var.media_cfg.channel_count / 1000;
3541 status = create_aud_param(&param, capture_dev, playback_dev,
3542 alt_cr[i], pjsua_var.media_cfg.channel_count,
3543 samples_per_frame, 16);
3544 if (status != PJ_SUCCESS)
3545 return status;
3546
3547 /* Open! */
3548 status = open_snd_dev(&param);
3549 if (status == PJ_SUCCESS)
3550 break;
3551 }
3552
3553 if (status != PJ_SUCCESS) {
3554 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
3555 return status;
3556 }
3557
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003558 pjsua_var.no_snd = PJ_FALSE;
3559
Benny Prijonof798e502009-03-09 13:08:16 +00003560 return PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003561}
3562
3563
3564/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00003565 * Get currently active sound devices. If sound devices has not been created
3566 * (for example when pjsua_start() is not called), it is possible that
3567 * the function returns PJ_SUCCESS with -1 as device IDs.
3568 */
3569PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
3570 int *playback_dev)
3571{
3572 if (capture_dev) {
3573 *capture_dev = pjsua_var.cap_dev;
3574 }
3575 if (playback_dev) {
3576 *playback_dev = pjsua_var.play_dev;
3577 }
3578
3579 return PJ_SUCCESS;
3580}
3581
3582
3583/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003584 * Use null sound device.
3585 */
3586PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
3587{
3588 pjmedia_port *conf_port;
3589 pj_status_t status;
3590
3591 /* Close existing sound device */
3592 close_snd_dev();
3593
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003594 /* Create memory pool for sound device. */
3595 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3596 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3597
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003598 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
3599
3600 /* Get the port0 of the conference bridge. */
3601 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3602 pj_assert(conf_port != NULL);
3603
3604 /* Create master port, connecting port0 of the conference bridge to
3605 * a null port.
3606 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003607 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003608 conf_port, 0, &pjsua_var.null_snd);
3609 if (status != PJ_SUCCESS) {
3610 pjsua_perror(THIS_FILE, "Unable to create null sound device",
3611 status);
3612 return status;
3613 }
3614
3615 /* Start the master port */
3616 status = pjmedia_master_port_start(pjsua_var.null_snd);
3617 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
3618
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003619 pjsua_var.cap_dev = NULL_SND_DEV_ID;
3620 pjsua_var.play_dev = NULL_SND_DEV_ID;
3621
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003622 pjsua_var.no_snd = PJ_FALSE;
3623
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003624 return PJ_SUCCESS;
3625}
3626
3627
Benny Prijonoe909eac2006-07-27 22:04:56 +00003628
3629/*
3630 * Use no device!
3631 */
3632PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
3633{
3634 /* Close existing sound device */
3635 close_snd_dev();
3636
3637 pjsua_var.no_snd = PJ_TRUE;
3638 return pjmedia_conf_get_master_port(pjsua_var.mconf);
3639}
3640
3641
Benny Prijonof20687a2006-08-04 18:27:19 +00003642/*
3643 * Configure the AEC settings of the sound port.
3644 */
Benny Prijono5da50432006-08-07 10:24:52 +00003645PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00003646{
3647 pjsua_var.media_cfg.ec_tail_len = tail_ms;
3648
3649 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00003650 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
3651 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00003652
3653 return PJ_SUCCESS;
3654}
3655
3656
3657/*
3658 * Get current AEC tail length.
3659 */
Benny Prijono22dfe592006-08-06 12:07:13 +00003660PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00003661{
3662 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
3663 return PJ_SUCCESS;
3664}
3665
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00003666
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003667/*
Benny Prijonof798e502009-03-09 13:08:16 +00003668 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003669 */
Benny Prijonof798e502009-03-09 13:08:16 +00003670PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003671{
Benny Prijonof798e502009-03-09 13:08:16 +00003672 return pjsua_var.snd_port != NULL;
3673}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003674
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003675
Benny Prijonof798e502009-03-09 13:08:16 +00003676/*
3677 * Configure sound device setting to the sound device being used.
3678 */
3679PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
3680 const void *pval,
3681 pj_bool_t keep)
3682{
Benny Prijono09b0ff62009-03-10 12:07:51 +00003683 pj_status_t status;
3684
Benny Prijonof798e502009-03-09 13:08:16 +00003685 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003686 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00003687 return PJMEDIA_EAUD_INVCAP;
3688 }
3689
Benny Prijono09b0ff62009-03-10 12:07:51 +00003690 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00003691 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00003692 pjmedia_aud_stream *strm;
3693
3694 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003695 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00003696 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00003697 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00003698 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00003699
3700 if (status != PJ_SUCCESS)
3701 return status;
3702
3703 /* Save in internal param for later device open */
3704 if (keep) {
3705 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
3706 cap, pval);
3707 }
3708
3709 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00003710}
3711
3712/*
3713 * Retrieve a sound device setting.
3714 */
3715PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
3716 void *pval)
3717{
3718 /* If sound device has never been opened before, open it to
3719 * retrieve the initial setting from the device (e.g. audio
3720 * volume)
3721 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003722 if (pjsua_var.aud_open_cnt==0) {
3723 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00003724 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003725 close_snd_dev();
3726 }
Benny Prijonof798e502009-03-09 13:08:16 +00003727
3728 if (pjsua_snd_is_active()) {
3729 /* Sound is active, retrieve from device directly */
3730 pjmedia_aud_stream *strm;
3731
3732 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3733 return pjmedia_aud_stream_get_cap(strm, cap, pval);
3734 } else {
3735 /* Otherwise retrieve from internal param */
3736 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
3737 cap, pval);
3738 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003739}
3740
3741
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003742/*****************************************************************************
3743 * Codecs.
3744 */
3745
3746/*
3747 * Enum all supported codecs in the system.
3748 */
3749PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
3750 unsigned *p_count )
3751{
3752 pjmedia_codec_mgr *codec_mgr;
3753 pjmedia_codec_info info[32];
3754 unsigned i, count, prio[32];
3755 pj_status_t status;
3756
3757 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3758 count = PJ_ARRAY_SIZE(info);
3759 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
3760 if (status != PJ_SUCCESS) {
3761 *p_count = 0;
3762 return status;
3763 }
3764
3765 if (count > *p_count) count = *p_count;
3766
3767 for (i=0; i<count; ++i) {
Nanang Izzuddin56b2ce42011-04-06 13:55:01 +00003768 pj_bzero(&id[i], sizeof(pjsua_codec_info));
3769
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003770 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
3771 id[i].codec_id = pj_str(id[i].buf_);
3772 id[i].priority = (pj_uint8_t) prio[i];
3773 }
3774
3775 *p_count = count;
3776
3777 return PJ_SUCCESS;
3778}
3779
3780
3781/*
3782 * Change codec priority.
3783 */
3784PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
3785 pj_uint8_t priority )
3786{
Benny Prijono88accae2008-06-26 15:48:14 +00003787 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003788 pjmedia_codec_mgr *codec_mgr;
3789
3790 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3791
Benny Prijono88accae2008-06-26 15:48:14 +00003792 if (codec_id->slen==1 && *codec_id->ptr=='*')
3793 codec_id = &all;
3794
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003795 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
3796 priority);
3797}
3798
3799
3800/*
3801 * Get codec parameters.
3802 */
3803PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
3804 pjmedia_codec_param *param )
3805{
Benny Prijono88accae2008-06-26 15:48:14 +00003806 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003807 const pjmedia_codec_info *info;
3808 pjmedia_codec_mgr *codec_mgr;
3809 unsigned count = 1;
3810 pj_status_t status;
3811
3812 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3813
Benny Prijono88accae2008-06-26 15:48:14 +00003814 if (codec_id->slen==1 && *codec_id->ptr=='*')
3815 codec_id = &all;
3816
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003817 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
3818 &count, &info, NULL);
3819 if (status != PJ_SUCCESS)
3820 return status;
3821
3822 if (count != 1)
Nanang Izzuddin50fae732011-03-22 09:49:23 +00003823 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003824
3825 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
3826 return status;
3827}
3828
3829
3830/*
3831 * Set codec parameters.
3832 */
Nanang Izzuddin06839e72010-01-27 11:48:31 +00003833PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003834 const pjmedia_codec_param *param)
3835{
Nanang Izzuddin06839e72010-01-27 11:48:31 +00003836 const pjmedia_codec_info *info[2];
3837 pjmedia_codec_mgr *codec_mgr;
3838 unsigned count = 2;
3839 pj_status_t status;
3840
3841 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3842
3843 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
3844 &count, info, NULL);
3845 if (status != PJ_SUCCESS)
3846 return status;
3847
3848 /* Codec ID should be specific, except for G.722.1 */
3849 if (count > 1 &&
3850 pj_strnicmp2(codec_id, "G7221/16", 8) != 0 &&
3851 pj_strnicmp2(codec_id, "G7221/32", 8) != 0)
3852 {
3853 pj_assert(!"Codec ID is not specific");
3854 return PJ_ETOOMANY;
3855 }
3856
3857 status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param);
3858 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003859}
Nanang Izzuddin50fae732011-03-22 09:49:23 +00003860
3861
3862#if PJMEDIA_HAS_VIDEO
3863
3864/*****************************************************************************
3865 * Video codecs.
3866 */
3867
3868/*
3869 * Enum all supported video codecs in the system.
3870 */
3871PJ_DEF(pj_status_t) pjsua_vid_enum_codecs( pjsua_codec_info id[],
3872 unsigned *p_count )
3873{
3874 pjmedia_vid_codec_info info[32];
3875 unsigned i, j, count, prio[32];
3876 pj_status_t status;
3877
3878 count = PJ_ARRAY_SIZE(info);
3879 status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio);
3880 if (status != PJ_SUCCESS) {
3881 *p_count = 0;
3882 return status;
3883 }
3884
3885 for (i=0, j=0; i<count && j<*p_count; ++i) {
3886 if (info[i].has_rtp_pack) {
Nanang Izzuddin56b2ce42011-04-06 13:55:01 +00003887 pj_bzero(&id[j], sizeof(pjsua_codec_info));
3888
Nanang Izzuddin50fae732011-03-22 09:49:23 +00003889 pjmedia_vid_codec_info_to_id(&info[i], id[j].buf_, sizeof(id[j].buf_));
3890 id[j].codec_id = pj_str(id[j].buf_);
3891 id[j].priority = (pj_uint8_t) prio[i];
Nanang Izzuddin56b2ce42011-04-06 13:55:01 +00003892
3893 if (id[j].codec_id.slen < sizeof(id[j].buf_)) {
3894 id[j].desc.ptr = id[j].codec_id.ptr + id[j].codec_id.slen + 1;
3895 pj_strncpy(&id[j].desc, &info[i].encoding_desc,
3896 sizeof(id[j].buf_) - id[j].codec_id.slen - 1);
3897 }
3898
Nanang Izzuddin50fae732011-03-22 09:49:23 +00003899 ++j;
3900 }
3901 }
3902
3903 *p_count = j;
3904
3905 return PJ_SUCCESS;
3906}
3907
3908
3909/*
3910 * Change video codec priority.
3911 */
3912PJ_DEF(pj_status_t) pjsua_vid_codec_set_priority( const pj_str_t *codec_id,
3913 pj_uint8_t priority )
3914{
3915 const pj_str_t all = { NULL, 0 };
3916
3917 if (codec_id->slen==1 && *codec_id->ptr=='*')
3918 codec_id = &all;
3919
3920 return pjmedia_vid_codec_mgr_set_codec_priority(NULL, codec_id,
3921 priority);
3922}
3923
3924
3925/*
3926 * Get video codec parameters.
3927 */
3928PJ_DEF(pj_status_t) pjsua_vid_codec_get_param(
3929 const pj_str_t *codec_id,
3930 pjmedia_vid_codec_param *param)
3931{
3932 const pj_str_t all = { NULL, 0 };
3933 const pjmedia_vid_codec_info *info;
3934 unsigned count = 1;
3935 pj_status_t status;
3936
3937 if (codec_id->slen==1 && *codec_id->ptr=='*')
3938 codec_id = &all;
3939
3940 status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
3941 &count, &info, NULL);
3942 if (status != PJ_SUCCESS)
3943 return status;
3944
3945 if (count != 1)
3946 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
3947
3948 status = pjmedia_vid_codec_mgr_get_default_param(NULL, info, param);
3949 return status;
3950}
3951
3952
3953/*
3954 * Set video codec parameters.
3955 */
3956PJ_DEF(pj_status_t) pjsua_vid_codec_set_param(
3957 const pj_str_t *codec_id,
3958 const pjmedia_vid_codec_param *param)
3959{
3960 const pjmedia_vid_codec_info *info[2];
3961 unsigned count = 2;
3962 pj_status_t status;
3963
3964 status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
3965 &count, info, NULL);
3966 if (status != PJ_SUCCESS)
3967 return status;
3968
3969 /* Codec ID should be specific */
3970 if (count > 1) {
3971 pj_assert(!"Codec ID is not specific");
3972 return PJ_ETOOMANY;
3973 }
3974
3975 status = pjmedia_vid_codec_mgr_set_default_param(NULL, pjsua_var.pool,
3976 info[0], param);
3977 return status;
3978}
3979
3980
3981/*****************************************************************************
3982 * Video devices.
3983 */
3984
3985/*
3986 * Enum all video devices installed in the system.
3987 */
3988PJ_DEF(pj_status_t) pjsua_vid_enum_devs(pjmedia_vid_dev_info info[],
3989 unsigned *count)
3990{
3991 unsigned i, dev_count;
3992
3993 dev_count = pjmedia_vid_dev_count();
3994
3995 if (dev_count > *count) dev_count = *count;
3996
3997 for (i=0; i<dev_count; ++i) {
3998 pj_status_t status;
3999
4000 status = pjmedia_vid_dev_get_info(i, &info[i]);
4001 if (status != PJ_SUCCESS)
4002 return status;
4003 }
4004
4005 *count = dev_count;
4006
4007 return PJ_SUCCESS;
4008}
4009
4010
4011/*
4012 * Get currently active video devices.
4013 */
4014PJ_DEF(pj_status_t) pjsua_vid_get_dev(int *capture_dev, int *render_dev)
4015{
4016 if (capture_dev)
4017 *capture_dev = pjsua_var.vcap_dev;
4018 if (render_dev)
4019 *render_dev = pjsua_var.vrdr_dev;
4020
4021 return PJ_SUCCESS;
4022}
4023
4024
4025/*
4026 * Select video device for the next video sessions.
4027 */
4028PJ_DEF(pj_status_t) pjsua_vid_set_dev(int capture_dev, int render_dev)
4029{
4030 pjmedia_vid_dev_info info;
4031 pj_status_t status;
4032
4033 if (capture_dev < 0)
4034 capture_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
4035 if (render_dev < 0)
4036 render_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV;
4037
4038 status = pjmedia_vid_dev_get_info(capture_dev, &info);
4039 if (status != PJ_SUCCESS)
4040 return status;
4041
4042 status = pjmedia_vid_dev_get_info(render_dev, &info);
4043 if (status != PJ_SUCCESS)
4044 return status;
4045
4046 pjsua_var.vcap_dev = capture_dev;
4047 pjsua_var.vrdr_dev = render_dev;
4048
4049 return PJ_SUCCESS;
4050}
4051
4052
4053/*
4054 * Configure video device setting to the video device being used.
4055 */
Nanang Izzuddin07fe8d32011-03-23 04:43:01 +00004056PJ_DEF(pj_status_t) pjsua_vid_set_setting(pjmedia_vid_dev_cap cap,
4057 const void *pval,
4058 pj_bool_t keep)
Nanang Izzuddin50fae732011-03-22 09:49:23 +00004059{
4060 PJ_UNUSED_ARG(cap);
4061 PJ_UNUSED_ARG(pval);
4062 PJ_UNUSED_ARG(keep);
4063 return PJ_ENOTSUP;
4064}
4065
4066
4067/*
4068 * Retrieve a video device setting.
4069 */
Nanang Izzuddin07fe8d32011-03-23 04:43:01 +00004070PJ_DEF(pj_status_t) pjsua_vid_get_setting(pjmedia_vid_dev_cap cap,
4071 void *pval)
Nanang Izzuddin50fae732011-03-22 09:49:23 +00004072{
4073 PJ_UNUSED_ARG(cap);
4074 PJ_UNUSED_ARG(pval);
4075 return PJ_ENOTSUP;
4076}
4077
4078#endif /* PJMEDIA_HAS_VIDEO */