blob: 0f56dd0ff8d56f6ca4c4f75d9fcca12b1bf5df75 [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
Nanang Izzuddina62ffc92011-05-05 06:14:19 +00003 * Copyright (C) 2008-2011 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 */
Sauw Ming98766c72011-03-11 06:57:24 +000039static pj_status_t open_snd_dev(pjmedia_snd_port_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 Prijono35fc1eb2011-07-15 09:51:46 +000069 pjmedia_audio_codec_config codec_cfg;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000070 pj_status_t status;
71
Benny Prijonofc24e692007-01-27 18:31:51 +000072 /* To suppress warning about unused var when all codecs are disabled */
73 PJ_UNUSED_ARG(codec_id);
74
Benny Prijonof798e502009-03-09 13:08:16 +000075 /* Specify which audio device settings are save-able */
76 pjsua_var.aud_svmask = 0xFFFFFFFF;
77 /* These are not-settable */
78 pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
79 PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER |
80 PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER);
Benny Prijonoe506c8c2009-03-10 13:28:43 +000081 /* EC settings use different API */
82 pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EC |
83 PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijonof798e502009-03-09 13:08:16 +000084
Benny Prijonoeebe9af2006-06-13 22:57:13 +000085 /* Copy configuration */
Benny Prijonof76e1392008-06-06 14:51:48 +000086 pjsua_media_config_dup(pjsua_var.pool, &pjsua_var.media_cfg, cfg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000087
88 /* Normalize configuration */
Benny Prijono50f19b32008-03-11 13:15:43 +000089 if (pjsua_var.media_cfg.snd_clock_rate == 0) {
90 pjsua_var.media_cfg.snd_clock_rate = pjsua_var.media_cfg.clock_rate;
91 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +000092
93 if (pjsua_var.media_cfg.has_ioqueue &&
94 pjsua_var.media_cfg.thread_cnt == 0)
95 {
96 pjsua_var.media_cfg.thread_cnt = 1;
97 }
98
99 if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
100 pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
101 }
102
103 /* Create media endpoint. */
104 status = pjmedia_endpt_create(&pjsua_var.cp.factory,
105 pjsua_var.media_cfg.has_ioqueue? NULL :
106 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
107 pjsua_var.media_cfg.thread_cnt,
108 &pjsua_var.med_endpt);
109 if (status != PJ_SUCCESS) {
110 pjsua_perror(THIS_FILE,
111 "Media stack initialization has returned error",
112 status);
113 return status;
114 }
115
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000116 /*
117 * Register all codecs
118 */
119 pjmedia_audio_codec_config_default(&codec_cfg);
120 codec_cfg.speex.quality = pjsua_var.media_cfg.quality;
121 codec_cfg.speex.complexity = -1;
122 codec_cfg.ilbc.mode = pjsua_var.media_cfg.ilbc_mode;
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000123
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000124#if PJMEDIA_HAS_PASSTHROUGH_CODECS
125 /* Register passthrough codecs */
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000126 {
127 unsigned aud_idx;
128 unsigned ext_fmt_cnt = 0;
129 pjmedia_format ext_fmts[32];
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000130
131 /* List extended formats supported by audio devices */
132 for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) {
133 pjmedia_aud_dev_info aud_info;
134 unsigned i;
135
136 status = pjmedia_aud_dev_get_info(aud_idx, &aud_info);
137 if (status != PJ_SUCCESS) {
138 pjsua_perror(THIS_FILE, "Error querying audio device info",
139 status);
140 return status;
141 }
142
143 /* Collect extended formats supported by this audio device */
144 for (i = 0; i < aud_info.ext_fmt_cnt; ++i) {
145 unsigned j;
146 pj_bool_t is_listed = PJ_FALSE;
147
148 /* See if this extended format is already in the list */
149 for (j = 0; j < ext_fmt_cnt && !is_listed; ++j) {
150 if (ext_fmts[j].id == aud_info.ext_fmt[i].id &&
151 ext_fmts[j].bitrate == aud_info.ext_fmt[i].bitrate)
152 {
153 is_listed = PJ_TRUE;
154 }
155 }
156
157 /* Put this format into the list, if it is not in the list */
158 if (!is_listed)
159 ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i];
160
161 pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts));
162 }
163 }
164
165 /* Init the passthrough codec with supported formats only */
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000166 codec_cfg.passthrough.setting.fmt_cnt = ext_fmt_cnt;
167 codec_cfg.passthrough.setting.fmts = ext_fmts;
168 codec_cfg.passthrough.setting.ilbc_mode = cfg->ilbc_mode;
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000169 }
170#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
171
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000172 /* Register all codecs */
173 status = pjmedia_codec_register_audio_codecs(pjsua_var.med_endpt,
174 &codec_cfg);
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000175 if (status != PJ_SUCCESS) {
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000176 PJ_PERROR(1,(THIS_FILE, status, "Error registering codecs"));
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000177 return status;
178 }
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000179
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000180 /* Set speex/16000 to higher priority*/
181 codec_id = pj_str("speex/16000");
182 pjmedia_codec_mgr_set_codec_priority(
183 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
184 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
185
186 /* Set speex/8000 to next higher priority*/
187 codec_id = pj_str("speex/8000");
188 pjmedia_codec_mgr_set_codec_priority(
189 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
190 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000191
192 /* Disable ALL L16 codecs */
193 codec_id = pj_str("L16");
194 pjmedia_codec_mgr_set_codec_priority(
195 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
196 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
197
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000198
199 /* Save additional conference bridge parameters for future
200 * reference.
201 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000202 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000203 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000204 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
205 pjsua_var.mconf_cfg.channel_count *
206 pjsua_var.media_cfg.audio_frame_ptime /
207 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000208
Benny Prijono0498d902006-06-19 14:49:14 +0000209 /* Init options for conference bridge. */
210 opt = PJMEDIA_CONF_NO_DEVICE;
211 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000212 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000213 {
214 opt |= PJMEDIA_CONF_SMALL_FILTER;
215 }
216 else if (pjsua_var.media_cfg.quality < 3) {
217 opt |= PJMEDIA_CONF_USE_LINEAR;
218 }
219
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000220 /* Init conference bridge. */
221 status = pjmedia_conf_create(pjsua_var.pool,
222 pjsua_var.media_cfg.max_media_ports,
223 pjsua_var.media_cfg.clock_rate,
224 pjsua_var.mconf_cfg.channel_count,
225 pjsua_var.mconf_cfg.samples_per_frame,
226 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000227 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000228 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000229 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000230 status);
231 return status;
232 }
233
Benny Prijonof798e502009-03-09 13:08:16 +0000234 /* Are we using the audio switchboard (a.k.a APS-Direct)? */
235 pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
236 ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
237
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000238 /* Create null port just in case user wants to use null sound. */
239 status = pjmedia_null_port_create(pjsua_var.pool,
240 pjsua_var.media_cfg.clock_rate,
241 pjsua_var.mconf_cfg.channel_count,
242 pjsua_var.mconf_cfg.samples_per_frame,
243 pjsua_var.mconf_cfg.bits_per_sample,
244 &pjsua_var.null_port);
245 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
246
Nanang Izzuddin69b69ae2009-04-14 15:18:30 +0000247#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
248 /* Initialize SRTP library. */
249 status = pjmedia_srtp_init_lib();
250 if (status != PJ_SUCCESS) {
251 pjsua_perror(THIS_FILE, "Error initializing SRTP library",
252 status);
253 return status;
254 }
255#endif
256
Benny Prijono9f468d12011-07-07 07:46:33 +0000257 /* Video */
258#if PJMEDIA_HAS_VIDEO
259 status = pjsua_vid_subsys_init();
260 if (status != PJ_SUCCESS)
261 return status;
262#endif
263
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000264 return PJ_SUCCESS;
265}
266
267
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000268/* Check if sound device is idle. */
269static void check_snd_dev_idle()
270{
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000271 unsigned call_cnt;
272
273 /* Get the call count, we shouldn't close the sound device when there is
274 * any calls active.
275 */
276 call_cnt = pjsua_call_get_count();
277
278 /* When this function is called from pjsua_media_channel_deinit() upon
279 * disconnecting call, actually the call count hasn't been updated/
280 * decreased. So we put additional check here, if there is only one
281 * call and it's in DISCONNECTED state, there is actually no active
282 * call.
283 */
284 if (call_cnt == 1) {
285 pjsua_call_id call_id;
286 pj_status_t status;
287
288 status = pjsua_enum_calls(&call_id, &call_cnt);
289 if (status == PJ_SUCCESS && call_cnt > 0 &&
290 !pjsua_call_is_active(call_id))
291 {
292 call_cnt = 0;
293 }
294 }
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000295
296 /* Activate sound device auto-close timer if sound device is idle.
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000297 * It is idle when there is no port connection in the bridge and
298 * there is no active call.
Benny Prijono2d647722011-07-13 03:05:22 +0000299 *
300 * Note: this block is now valid if no snd dev is used because of #1299
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000301 */
Benny Prijono2d647722011-07-13 03:05:22 +0000302 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL ||
303 pjsua_var.no_snd) &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000304 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
305 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000306 call_cnt == 0 &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000307 pjsua_var.media_cfg.snd_auto_close_time >= 0)
308 {
309 pj_time_val delay;
310
311 delay.msec = 0;
312 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
313
314 pjsua_var.snd_idle_timer.id = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000315 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000316 &delay);
317 }
318}
319
320
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000321/* Timer callback to close sound device */
322static void close_snd_timer_cb( pj_timer_heap_t *th,
323 pj_timer_entry *entry)
324{
325 PJ_UNUSED_ARG(th);
326
Benny Prijono0f711b42009-05-06 19:08:43 +0000327 PJSUA_LOCK();
328 if (entry->id) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000329 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
Benny Prijono0f711b42009-05-06 19:08:43 +0000330 pjsua_var.media_cfg.snd_auto_close_time));
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000331
Benny Prijono0f711b42009-05-06 19:08:43 +0000332 entry->id = PJ_FALSE;
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000333
Benny Prijono0f711b42009-05-06 19:08:43 +0000334 close_snd_dev();
335 }
336 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000337}
338
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000339
340/*
341 * Start pjsua media subsystem.
342 */
343pj_status_t pjsua_media_subsys_start(void)
344{
345 pj_status_t status;
346
Benny Prijono0bc99a92011-03-17 04:34:43 +0000347#if DISABLED_FOR_TICKET_1185
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000348 /* Create media for calls, if none is specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000349 if (pjsua_var.calls[0].media[0].tp == NULL) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000350 pjsua_transport_config transport_cfg;
351
352 /* Create default transport config */
353 pjsua_transport_config_default(&transport_cfg);
354 transport_cfg.port = DEFAULT_RTP_PORT;
355
356 status = pjsua_media_transports_create(&transport_cfg);
357 if (status != PJ_SUCCESS)
358 return status;
359 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000360#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000361
Benny Prijono0bc99a92011-03-17 04:34:43 +0000362 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000363 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000364
Benny Prijono9f468d12011-07-07 07:46:33 +0000365 /* Video */
366#if PJMEDIA_HAS_VIDEO
367 status = pjsua_vid_subsys_start();
368 if (status != PJ_SUCCESS)
369 return status;
370#endif
371
Benny Prijonobf53b002010-01-04 13:08:31 +0000372 /* Perform NAT detection */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000373 status = pjsua_detect_nat_type();
374 if (status != PJ_SUCCESS) {
375 PJ_PERROR(1,(THIS_FILE, status, "NAT type detection failed"));
376 }
Benny Prijonobf53b002010-01-04 13:08:31 +0000377
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000378 return PJ_SUCCESS;
379}
380
381
382/*
383 * Destroy pjsua media subsystem.
384 */
385pj_status_t pjsua_media_subsys_destroy(void)
386{
387 unsigned i;
388
Benny Prijono384dab42009-10-14 01:58:04 +0000389 PJ_LOG(4,(THIS_FILE, "Shutting down media.."));
390
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000391 close_snd_dev();
392
393 if (pjsua_var.mconf) {
394 pjmedia_conf_destroy(pjsua_var.mconf);
395 pjsua_var.mconf = NULL;
396 }
397
398 if (pjsua_var.null_port) {
399 pjmedia_port_destroy(pjsua_var.null_port);
400 pjsua_var.null_port = NULL;
401 }
402
403 /* Destroy file players */
404 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
405 if (pjsua_var.player[i].port) {
406 pjmedia_port_destroy(pjsua_var.player[i].port);
407 pjsua_var.player[i].port = NULL;
408 }
409 }
410
411 /* Destroy file recorders */
412 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
413 if (pjsua_var.recorder[i].port) {
414 pjmedia_port_destroy(pjsua_var.recorder[i].port);
415 pjsua_var.recorder[i].port = NULL;
416 }
417 }
418
419 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000420 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000421 unsigned strm_idx;
422 pjsua_call *call = &pjsua_var.calls[i];
423 for (strm_idx=0; strm_idx<call->med_cnt; ++strm_idx) {
424 pjsua_call_media *call_med = &call->media[strm_idx];
425 if (call_med->tp_st != PJSUA_MED_TP_IDLE) {
426 pjsua_media_channel_deinit(i);
427 }
428 if (call_med->tp && call_med->tp_auto_del) {
429 pjmedia_transport_close(call_med->tp);
430 }
431 call_med->tp = NULL;
Benny Prijono311b63f2008-07-14 11:31:40 +0000432 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000433 }
434
435 /* Destroy media endpoint. */
436 if (pjsua_var.med_endpt) {
437
Benny Prijono0bc99a92011-03-17 04:34:43 +0000438# if PJMEDIA_HAS_VIDEO
Benny Prijono9f468d12011-07-07 07:46:33 +0000439 pjsua_vid_subsys_destroy();
Benny Prijono0bc99a92011-03-17 04:34:43 +0000440# endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000441
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000442 pjmedia_endpt_destroy(pjsua_var.med_endpt);
443 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000444
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000445 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000446 // Not necessary, as pjmedia_snd_deinit() should have been called
447 // in pjmedia_endpt_destroy().
448 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000449 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000450
Benny Prijonode479562007-03-15 10:23:55 +0000451 /* Reset RTP port */
452 next_rtp_port = 0;
453
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000454 return PJ_SUCCESS;
455}
456
Benny Prijono0bc99a92011-03-17 04:34:43 +0000457/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000458 * Create RTP and RTCP socket pair, and possibly resolve their public
459 * address via STUN.
460 */
461static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
462 pjmedia_sock_info *skinfo)
463{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000464 enum {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000465 RTP_RETRY = 100
466 };
467 int i;
468 pj_sockaddr_in bound_addr;
469 pj_sockaddr_in mapped_addr[2];
470 pj_status_t status = PJ_SUCCESS;
471 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
472 pj_sock_t sock[2];
473
474 /* Make sure STUN server resolution has completed */
475 status = resolve_stun_server(PJ_TRUE);
476 if (status != PJ_SUCCESS) {
477 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
478 return status;
479 }
480
481 if (next_rtp_port == 0)
482 next_rtp_port = (pj_uint16_t)cfg->port;
483
Benny Prijono0bc99a92011-03-17 04:34:43 +0000484 if (next_rtp_port == 0)
485 next_rtp_port = (pj_uint16_t)40000;
486
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000487 for (i=0; i<2; ++i)
488 sock[i] = PJ_INVALID_SOCKET;
489
490 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
491 if (cfg->bound_addr.slen) {
492 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
493 if (status != PJ_SUCCESS) {
494 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
495 status);
496 return status;
497 }
498 }
499
500 /* Loop retry to bind RTP and RTCP sockets. */
501 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
502
503 /* Create RTP socket. */
504 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
505 if (status != PJ_SUCCESS) {
506 pjsua_perror(THIS_FILE, "socket() error", status);
507 return status;
508 }
509
510 /* Apply QoS to RTP socket, if specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000511 status = pj_sock_apply_qos2(sock[0], cfg->qos_type,
512 &cfg->qos_params,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000513 2, THIS_FILE, "RTP socket");
514
515 /* Bind RTP socket */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000516 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000517 next_rtp_port);
518 if (status != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000519 pj_sock_close(sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000520 sock[0] = PJ_INVALID_SOCKET;
521 continue;
522 }
523
524 /* Create RTCP socket. */
525 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
526 if (status != PJ_SUCCESS) {
527 pjsua_perror(THIS_FILE, "socket() error", status);
528 pj_sock_close(sock[0]);
529 return status;
530 }
531
532 /* Apply QoS to RTCP socket, if specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000533 status = pj_sock_apply_qos2(sock[1], cfg->qos_type,
534 &cfg->qos_params,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000535 2, THIS_FILE, "RTCP socket");
536
537 /* Bind RTCP socket */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000538 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000539 (pj_uint16_t)(next_rtp_port+1));
540 if (status != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000541 pj_sock_close(sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000542 sock[0] = PJ_INVALID_SOCKET;
543
Benny Prijono0bc99a92011-03-17 04:34:43 +0000544 pj_sock_close(sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000545 sock[1] = PJ_INVALID_SOCKET;
546 continue;
547 }
548
549 /*
550 * If we're configured to use STUN, then find out the mapped address,
551 * and make sure that the mapped RTCP port is adjacent with the RTP.
552 */
553 if (pjsua_var.stun_srv.addr.sa_family != 0) {
554 char ip_addr[32];
555 pj_str_t stun_srv;
556
Benny Prijono0bc99a92011-03-17 04:34:43 +0000557 pj_ansi_strcpy(ip_addr,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000558 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
559 stun_srv = pj_str(ip_addr);
560
561 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
562 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
563 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
564 mapped_addr);
565 if (status != PJ_SUCCESS) {
566 pjsua_perror(THIS_FILE, "STUN resolve error", status);
567 goto on_error;
568 }
569
570#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijono0bc99a92011-03-17 04:34:43 +0000571 if (pj_ntohs(mapped_addr[1].sin_port) ==
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000572 pj_ntohs(mapped_addr[0].sin_port)+1)
573 {
574 /* Success! */
575 break;
576 }
577
Benny Prijono0bc99a92011-03-17 04:34:43 +0000578 pj_sock_close(sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000579 sock[0] = PJ_INVALID_SOCKET;
580
Benny Prijono0bc99a92011-03-17 04:34:43 +0000581 pj_sock_close(sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000582 sock[1] = PJ_INVALID_SOCKET;
583#else
Benny Prijono0bc99a92011-03-17 04:34:43 +0000584 if (pj_ntohs(mapped_addr[1].sin_port) !=
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000585 pj_ntohs(mapped_addr[0].sin_port)+1)
586 {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000587 PJ_LOG(4,(THIS_FILE,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000588 "Note: STUN mapped RTCP port %d is not adjacent"
589 " to RTP port %d",
590 pj_ntohs(mapped_addr[1].sin_port),
591 pj_ntohs(mapped_addr[0].sin_port)));
592 }
593 /* Success! */
594 break;
595#endif
596
597 } else if (cfg->public_addr.slen) {
598
599 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
600 (pj_uint16_t)next_rtp_port);
601 if (status != PJ_SUCCESS)
602 goto on_error;
603
604 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
605 (pj_uint16_t)(next_rtp_port+1));
606 if (status != PJ_SUCCESS)
607 goto on_error;
608
609 break;
610
611 } else {
612
613 if (bound_addr.sin_addr.s_addr == 0) {
614 pj_sockaddr addr;
615
616 /* Get local IP address. */
617 status = pj_gethostip(pj_AF_INET(), &addr);
618 if (status != PJ_SUCCESS)
619 goto on_error;
620
621 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
622 }
623
624 for (i=0; i<2; ++i) {
625 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
626 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
627 }
628
629 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
630 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
631 break;
632 }
633 }
634
635 if (sock[0] == PJ_INVALID_SOCKET) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000636 PJ_LOG(1,(THIS_FILE,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000637 "Unable to find appropriate RTP/RTCP ports combination"));
638 goto on_error;
639 }
640
641
642 skinfo->rtp_sock = sock[0];
Benny Prijono0bc99a92011-03-17 04:34:43 +0000643 pj_memcpy(&skinfo->rtp_addr_name,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000644 &mapped_addr[0], sizeof(pj_sockaddr_in));
645
646 skinfo->rtcp_sock = sock[1];
Benny Prijono0bc99a92011-03-17 04:34:43 +0000647 pj_memcpy(&skinfo->rtcp_addr_name,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000648 &mapped_addr[1], sizeof(pj_sockaddr_in));
649
650 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
651 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
652 sizeof(addr_buf), 3)));
653 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
654 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
655 sizeof(addr_buf), 3)));
656
657 next_rtp_port += 2;
658 return PJ_SUCCESS;
659
660on_error:
661 for (i=0; i<2; ++i) {
662 if (sock[i] != PJ_INVALID_SOCKET)
663 pj_sock_close(sock[i]);
664 }
665 return status;
666}
667
Benny Prijono0bc99a92011-03-17 04:34:43 +0000668/* Create normal UDP media transports */
669static pj_status_t create_udp_media_transport(const pjsua_transport_config *cfg,
670 pjsua_call_media *call_med)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000671{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000672 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000673 pj_status_t status;
674
Benny Prijono0bc99a92011-03-17 04:34:43 +0000675 status = create_rtp_rtcp_sock(cfg, &skinfo);
676 if (status != PJ_SUCCESS) {
677 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
678 status);
679 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000680 }
681
Benny Prijono0bc99a92011-03-17 04:34:43 +0000682 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
683 &skinfo, 0, &call_med->tp);
684 if (status != PJ_SUCCESS) {
685 pjsua_perror(THIS_FILE, "Unable to create media transport",
686 status);
687 goto on_error;
688 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000689
Benny Prijono0bc99a92011-03-17 04:34:43 +0000690 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
691 pjsua_var.media_cfg.tx_drop_pct);
692
693 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
694 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000695
696 return PJ_SUCCESS;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000697
698on_error:
699 if (call_med->tp)
700 pjmedia_transport_close(call_med->tp);
701
702 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000703}
704
Benny Prijono0bc99a92011-03-17 04:34:43 +0000705#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +0000706/* Create normal UDP media transports */
707static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000708{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000709 unsigned i;
710 pj_status_t status;
711
Benny Prijono0bc99a92011-03-17 04:34:43 +0000712 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
713 pjsua_call *call = &pjsua_var.calls[i];
714 unsigned strm_idx;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000715
Benny Prijono0bc99a92011-03-17 04:34:43 +0000716 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
717 pjsua_call_media *call_med = &call->media[strm_idx];
718
719 status = create_udp_media_transport(cfg, &call_med->tp);
720 if (status != PJ_SUCCESS)
721 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000722 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000723 }
724
Benny Prijonoc97608e2007-03-23 16:34:20 +0000725 return PJ_SUCCESS;
726
727on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000728 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
729 pjsua_call *call = &pjsua_var.calls[i];
730 unsigned strm_idx;
731
732 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
733 pjsua_call_media *call_med = &call->media[strm_idx];
734
735 if (call_med->tp) {
736 pjmedia_transport_close(call_med->tp);
737 call_med->tp = NULL;
738 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000739 }
740 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000741 return status;
742}
Benny Prijono0bc99a92011-03-17 04:34:43 +0000743#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000744
Benny Prijono096c56c2007-09-15 08:30:16 +0000745/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000746static void on_ice_complete(pjmedia_transport *tp,
747 pj_ice_strans_op op,
748 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000749{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000750 pjsua_call_media *call_med = (pjsua_call_media*)tp->user_data;
Benny Prijono096c56c2007-09-15 08:30:16 +0000751
Benny Prijono0bc99a92011-03-17 04:34:43 +0000752 if (!call_med)
Benny Prijonof76e1392008-06-06 14:51:48 +0000753 return;
754
755 switch (op) {
756 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000757 call_med->tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000758 break;
759 case PJ_ICE_STRANS_OP_NEGOTIATION:
760 if (result != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000761 call_med->state = PJSUA_CALL_MEDIA_ERROR;
762 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijonof76e1392008-06-06 14:51:48 +0000763
Benny Prijono0bc99a92011-03-17 04:34:43 +0000764 if (call_med->call && pjsua_var.ua_cfg.cb.on_call_media_state) {
765 pjsua_var.ua_cfg.cb.on_call_media_state(call_med->call->index);
Benny Prijonof76e1392008-06-06 14:51:48 +0000766 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000767 } else if (call_med->call) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000768 /* Send UPDATE if default transport address is different than
769 * what was advertised (ticket #881)
770 */
771 pjmedia_transport_info tpinfo;
772 pjmedia_ice_transport_info *ii = NULL;
773 unsigned i;
774
775 pjmedia_transport_info_init(&tpinfo);
776 pjmedia_transport_get_info(tp, &tpinfo);
777 for (i=0; i<tpinfo.specific_info_cnt; ++i) {
778 if (tpinfo.spc_info[i].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
779 ii = (pjmedia_ice_transport_info*)
780 tpinfo.spc_info[i].buffer;
781 break;
782 }
783 }
784
785 if (ii && ii->role==PJ_ICE_SESS_ROLE_CONTROLLING &&
786 pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000787 &call_med->rtp_addr))
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000788 {
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000789 pj_bool_t use_update;
790 const pj_str_t STR_UPDATE = { "UPDATE", 6 };
791 pjsip_dialog_cap_status support_update;
792 pjsip_dialog *dlg;
793
Benny Prijono0bc99a92011-03-17 04:34:43 +0000794 dlg = call_med->call->inv->dlg;
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000795 support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW,
796 NULL, &STR_UPDATE);
797 use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED);
798
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000799 PJ_LOG(4,(THIS_FILE,
800 "ICE default transport address has changed for "
Benny Prijono0bc99a92011-03-17 04:34:43 +0000801 "call %d, sending %s", call_med->call->index,
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000802 (use_update ? "UPDATE" : "re-INVITE")));
803
804 if (use_update)
Benny Prijono0bc99a92011-03-17 04:34:43 +0000805 pjsua_call_update(call_med->call->index, 0, NULL);
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000806 else
Benny Prijono0bc99a92011-03-17 04:34:43 +0000807 pjsua_call_reinvite(call_med->call->index, 0, NULL);
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000808 }
Benny Prijonof76e1392008-06-06 14:51:48 +0000809 }
810 break;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000811 case PJ_ICE_STRANS_OP_KEEP_ALIVE:
812 if (result != PJ_SUCCESS) {
813 PJ_PERROR(4,(THIS_FILE, result,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000814 "ICE keep alive failure for transport %d:%d",
815 call_med->call->index, call_med->idx));
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000816 }
817 if (pjsua_var.ua_cfg.cb.on_ice_transport_error) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000818 pjsua_call_id id = call_med->call->index;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000819 (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result,
820 NULL);
821 }
822 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000823 }
824}
825
826
Benny Prijonof76e1392008-06-06 14:51:48 +0000827/* Parse "HOST:PORT" format */
828static pj_status_t parse_host_port(const pj_str_t *host_port,
829 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000830{
Benny Prijonof76e1392008-06-06 14:51:48 +0000831 pj_str_t str_port;
832
833 str_port.ptr = pj_strchr(host_port, ':');
834 if (str_port.ptr != NULL) {
835 int iport;
836
837 host->ptr = host_port->ptr;
838 host->slen = (str_port.ptr - host->ptr);
839 str_port.ptr++;
840 str_port.slen = host_port->slen - host->slen - 1;
841 iport = (int)pj_strtoul(&str_port);
842 if (iport < 1 || iport > 65535)
843 return PJ_EINVAL;
844 *port = (pj_uint16_t)iport;
845 } else {
846 *host = *host_port;
847 *port = 0;
848 }
849
850 return PJ_SUCCESS;
851}
852
853/* Create ICE media transports (when ice is enabled) */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000854static pj_status_t create_ice_media_transport(
855 const pjsua_transport_config *cfg,
856 pjsua_call_media *call_med)
Benny Prijonof76e1392008-06-06 14:51:48 +0000857{
858 char stunip[PJ_INET6_ADDRSTRLEN];
859 pj_ice_strans_cfg ice_cfg;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000860 pjmedia_ice_cb ice_cb;
861 char name[32];
862 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000863 pj_status_t status;
864
Benny Prijonoda9785b2007-04-02 20:43:06 +0000865 /* Make sure STUN server resolution has completed */
Benny Prijonobb995fd2009-08-12 11:03:23 +0000866 status = resolve_stun_server(PJ_TRUE);
Benny Prijonoda9785b2007-04-02 20:43:06 +0000867 if (status != PJ_SUCCESS) {
868 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
869 return status;
870 }
871
Benny Prijonof76e1392008-06-06 14:51:48 +0000872 /* Create ICE stream transport configuration */
873 pj_ice_strans_cfg_default(&ice_cfg);
874 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
875 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
876 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
877
878 ice_cfg.af = pj_AF_INET();
879 ice_cfg.resolver = pjsua_var.resolver;
880
Benny Prijono329d6382009-05-29 13:04:03 +0000881 ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
882
Benny Prijonof76e1392008-06-06 14:51:48 +0000883 /* Configure STUN settings */
884 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
885 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
886 ice_cfg.stun.server = pj_str(stunip);
887 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
888 }
Benny Prijono329d6382009-05-29 13:04:03 +0000889 if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
890 ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
Benny Prijonof76e1392008-06-06 14:51:48 +0000891
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000892 /* Copy QoS setting to STUN setting */
893 ice_cfg.stun.cfg.qos_type = cfg->qos_type;
894 pj_memcpy(&ice_cfg.stun.cfg.qos_params, &cfg->qos_params,
895 sizeof(cfg->qos_params));
896
Benny Prijonof76e1392008-06-06 14:51:48 +0000897 /* Configure TURN settings */
898 if (pjsua_var.media_cfg.enable_turn) {
899 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
900 &ice_cfg.turn.server,
901 &ice_cfg.turn.port);
902 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
903 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
904 return PJ_EINVAL;
905 }
906 if (ice_cfg.turn.port == 0)
907 ice_cfg.turn.port = 3479;
908 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
909 pj_memcpy(&ice_cfg.turn.auth_cred,
910 &pjsua_var.media_cfg.turn_auth_cred,
911 sizeof(ice_cfg.turn.auth_cred));
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000912
913 /* Copy QoS setting to TURN setting */
914 ice_cfg.turn.cfg.qos_type = cfg->qos_type;
915 pj_memcpy(&ice_cfg.turn.cfg.qos_params, &cfg->qos_params,
916 sizeof(cfg->qos_params));
Benny Prijonof76e1392008-06-06 14:51:48 +0000917 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000918
Benny Prijono0bc99a92011-03-17 04:34:43 +0000919 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
920 ice_cb.on_ice_complete = &on_ice_complete;
921 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", call_med->idx);
922 call_med->tp_ready = PJ_EPENDING;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000923
Benny Prijono0bc99a92011-03-17 04:34:43 +0000924 comp_cnt = 1;
925 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
926 ++comp_cnt;
Benny Prijonof76e1392008-06-06 14:51:48 +0000927
Benny Prijonobd6613f2011-04-11 17:27:14 +0000928 status = pjmedia_ice_create3(pjsua_var.med_endpt, name, comp_cnt,
929 &ice_cfg, &ice_cb, 0, call_med,
930 &call_med->tp);
Benny Prijono0bc99a92011-03-17 04:34:43 +0000931 if (status != PJ_SUCCESS) {
932 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
933 status);
934 goto on_error;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000935 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000936
Benny Prijono0bc99a92011-03-17 04:34:43 +0000937 /* Wait until transport is initialized, or time out */
938 PJSUA_UNLOCK();
939 while (call_med->tp_ready == PJ_EPENDING) {
940 pjsua_handle_events(100);
941 }
942 PJSUA_LOCK();
943 if (call_med->tp_ready != PJ_SUCCESS) {
944 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
945 call_med->tp_ready);
946 status = call_med->tp_ready;
947 goto on_error;
948 }
949
950 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
951 pjsua_var.media_cfg.tx_drop_pct);
952
953 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
954 pjsua_var.media_cfg.rx_drop_pct);
955
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000956 return PJ_SUCCESS;
957
958on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000959 if (call_med->tp != NULL) {
960 pjmedia_transport_close(call_med->tp);
961 call_med->tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000962 }
963
Benny Prijonoc97608e2007-03-23 16:34:20 +0000964 return status;
965}
966
Benny Prijono0bc99a92011-03-17 04:34:43 +0000967#if DISABLED_FOR_TICKET_1185
968/* Create ICE media transports (when ice is enabled) */
969static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
970{
971 unsigned i;
972 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000973
Benny Prijono0bc99a92011-03-17 04:34:43 +0000974 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
975 pjsua_call *call = &pjsua_var.calls[i];
976 unsigned strm_idx;
977
978 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
979 pjsua_call_media *call_med = &call->media[strm_idx];
980
981 status = create_ice_media_transport(cfg, call_med);
982 if (status != PJ_SUCCESS)
983 goto on_error;
984 }
985 }
986
987 return PJ_SUCCESS;
988
989on_error:
990 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
991 pjsua_call *call = &pjsua_var.calls[i];
992 unsigned strm_idx;
993
994 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
995 pjsua_call_media *call_med = &call->media[strm_idx];
996
997 if (call_med->tp) {
998 pjmedia_transport_close(call_med->tp);
999 call_med->tp = NULL;
1000 }
1001 }
1002 }
1003 return status;
1004}
1005#endif
1006
1007#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +00001008/*
Benny Prijono0bc99a92011-03-17 04:34:43 +00001009 * Create media transports for all the calls. This function creates
Benny Prijonoc97608e2007-03-23 16:34:20 +00001010 * one UDP media transport for each call.
1011 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001012PJ_DEF(pj_status_t) pjsua_media_transports_create(
1013 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001014{
1015 pjsua_transport_config cfg;
1016 unsigned i;
1017 pj_status_t status;
1018
1019
1020 /* Make sure pjsua_init() has been called */
1021 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
1022
1023 PJSUA_LOCK();
1024
1025 /* Delete existing media transports */
1026 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001027 pjsua_call *call = &pjsua_var.calls[i];
1028 unsigned strm_idx;
1029
1030 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1031 pjsua_call_media *call_med = &call->media[strm_idx];
1032
1033 if (call_med->tp && call_med->tp_auto_del) {
1034 pjmedia_transport_close(call_med->tp);
1035 call_med->tp = NULL;
1036 call_med->tp_orig = NULL;
1037 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001038 }
1039 }
1040
1041 /* Copy config */
1042 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
1043
Benny Prijono40860c32008-09-04 13:55:33 +00001044 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001045 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijono4d79b0f2009-10-25 09:02:07 +00001046 status = create_ice_media_transports(&cfg);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001047 } else {
1048 status = create_udp_media_transports(&cfg);
1049 }
1050
Benny Prijono40860c32008-09-04 13:55:33 +00001051 /* Set media transport auto_delete to True */
1052 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001053 pjsua_call *call = &pjsua_var.calls[i];
1054 unsigned strm_idx;
1055
1056 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1057 pjsua_call_media *call_med = &call->media[strm_idx];
1058
1059 call_med->tp_auto_del = PJ_TRUE;
1060 }
Benny Prijono40860c32008-09-04 13:55:33 +00001061 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001062
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001063 PJSUA_UNLOCK();
1064
1065 return status;
1066}
1067
Benny Prijono40860c32008-09-04 13:55:33 +00001068/*
1069 * Attach application's created media transports.
1070 */
1071PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
1072 unsigned count,
1073 pj_bool_t auto_delete)
1074{
1075 unsigned i;
1076
1077 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
1078
1079 /* Assign the media transports */
1080 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001081 pjsua_call *call = &pjsua_var.calls[i];
1082 unsigned strm_idx;
1083
1084 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1085 pjsua_call_media *call_med = &call->media[strm_idx];
1086
1087 if (call_med->tp && call_med->tp_auto_del) {
1088 pjmedia_transport_close(call_med->tp);
1089 call_med->tp = NULL;
1090 call_med->tp_orig = NULL;
1091 }
Benny Prijono40860c32008-09-04 13:55:33 +00001092 }
1093
Benny Prijono0bc99a92011-03-17 04:34:43 +00001094 PJ_TODO(remove_pjsua_media_transports_attach);
1095
1096 call->media[0].tp = tp[i].transport;
1097 call->media[0].tp_auto_del = auto_delete;
Benny Prijono40860c32008-09-04 13:55:33 +00001098 }
1099
1100 return PJ_SUCCESS;
1101}
Benny Prijono0bc99a92011-03-17 04:34:43 +00001102#endif
Benny Prijono40860c32008-09-04 13:55:33 +00001103
Benny Prijono0bc99a92011-03-17 04:34:43 +00001104/* Go through the list of media in the SDP, find acceptable media, and
1105 * sort them based on the "quality" of the media, and store the indexes
1106 * in the specified array. Media with the best quality will be listed
1107 * first in the array. The quality factors considered currently is
1108 * encryption.
1109 */
1110static void sort_media(const pjmedia_sdp_session *sdp,
1111 const pj_str_t *type,
1112 pjmedia_srtp_use use_srtp,
1113 pj_uint8_t midx[],
1114 unsigned *p_count)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001115{
1116 unsigned i;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001117 unsigned count = 0;
1118 int score[PJSUA_MAX_CALL_MEDIA];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001119
Benny Prijono0bc99a92011-03-17 04:34:43 +00001120 pj_assert(*p_count >= PJSUA_MAX_CALL_MEDIA);
1121
1122 *p_count = 0;
Benny Prijono09c0d672011-04-11 05:03:24 +00001123 for (i=0; i<PJSUA_MAX_CALL_MEDIA; ++i)
1124 score[i] = 1;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001125
1126 /* Score each media */
1127 for (i=0; i<sdp->media_count && count<PJSUA_MAX_CALL_MEDIA; ++i) {
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001128 const pjmedia_sdp_media *m = sdp->media[i];
Nanang Izzuddina6414292011-04-08 04:26:18 +00001129 const pjmedia_sdp_conn *c;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001130
Benny Prijono0bc99a92011-03-17 04:34:43 +00001131 /* Skip different media */
1132 if (pj_stricmp(&m->desc.media, type) != 0) {
1133 score[count++] = -22000;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001134 continue;
1135 }
1136
Nanang Izzuddina6414292011-04-08 04:26:18 +00001137 c = m->conn? m->conn : sdp->conn;
1138
Benny Prijono0bc99a92011-03-17 04:34:43 +00001139 /* Supported transports */
1140 if (pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0) {
1141 switch (use_srtp) {
1142 case PJMEDIA_SRTP_MANDATORY:
1143 case PJMEDIA_SRTP_OPTIONAL:
1144 ++score[i];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001145 break;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001146 case PJMEDIA_SRTP_DISABLED:
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001147 //--score[i];
1148 score[i] -= 5;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001149 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001150 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001151 } else if (pj_stricmp2(&m->desc.transport, "RTP/AVP")==0) {
1152 switch (use_srtp) {
1153 case PJMEDIA_SRTP_MANDATORY:
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001154 //--score[i];
1155 score[i] -= 5;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001156 break;
1157 case PJMEDIA_SRTP_OPTIONAL:
1158 /* No change in score */
1159 break;
1160 case PJMEDIA_SRTP_DISABLED:
1161 ++score[i];
1162 break;
1163 }
1164 } else {
1165 score[i] -= 10;
1166 }
1167
1168 /* Is media disabled? */
1169 if (m->desc.port == 0)
1170 score[i] -= 10;
1171
1172 /* Is media inactive? */
Nanang Izzuddina6414292011-04-08 04:26:18 +00001173 if (pjmedia_sdp_media_find_attr2(m, "inactive", NULL) ||
1174 pj_strcmp2(&c->addr, "0.0.0.0") == 0)
1175 {
1176 //score[i] -= 10;
1177 score[i] -= 1;
1178 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001179
1180 ++count;
1181 }
1182
1183 /* Created sorted list based on quality */
1184 for (i=0; i<count; ++i) {
1185 unsigned j;
1186 int best = 0;
1187
1188 for (j=1; j<count; ++j) {
1189 if (score[j] > score[best])
1190 best = j;
1191 }
1192 /* Don't put media with negative score, that media is unacceptable
1193 * for us.
1194 */
1195 if (score[best] >= 0) {
1196 midx[*p_count] = (pj_uint8_t)best;
1197 (*p_count)++;
1198 }
1199
1200 score[best] = -22000;
1201
1202 }
1203}
1204
Benny Prijonoee0ba182011-07-15 06:18:29 +00001205/* Callback to receive media events */
1206static pj_status_t call_media_on_event(pjmedia_event_subscription *esub,
1207 pjmedia_event *event)
1208{
1209 pjsua_call_media *call_med = (pjsua_call_media*)esub->user_data;
1210 pjsua_call *call = call_med->call;
1211
1212 if (pjsua_var.ua_cfg.cb.on_call_media_event && call) {
1213 ++event->proc_cnt;
1214 (*pjsua_var.ua_cfg.cb.on_call_media_event)(call->index,
1215 call_med->idx, event);
Benny Prijono53a7c702008-04-14 02:57:29 +00001216 }
1217
1218 return PJ_SUCCESS;
1219}
1220
Benny Prijono0bc99a92011-03-17 04:34:43 +00001221/* Initialize the media line */
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001222pj_status_t pjsua_call_media_init(pjsua_call_media *call_med,
1223 pjmedia_type type,
1224 const pjsua_transport_config *tcfg,
1225 int security_level,
1226 int *sip_err_code)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001227{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001228 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
1229 pj_status_t status;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001230
Benny Prijono0bc99a92011-03-17 04:34:43 +00001231 /*
1232 * Note: this function may be called when the media already exists
1233 * (e.g. in reinvites, updates, etc.)
1234 */
1235 call_med->type = type;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001236
Benny Prijono0bc99a92011-03-17 04:34:43 +00001237 /* Create the media transport for initial call. This is blocking for now */
1238 if (call_med->tp == NULL) {
1239 if (pjsua_var.media_cfg.enable_ice) {
1240 status = create_ice_media_transport(tcfg, call_med);
1241 } else {
1242 status = create_udp_media_transport(tcfg, call_med);
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001243 }
1244
Benny Prijono0bc99a92011-03-17 04:34:43 +00001245 if (status != PJ_SUCCESS) {
1246 PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport"));
1247 return status;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001248 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001249
1250 call_med->tp_st = PJSUA_MED_TP_IDLE;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001251
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00001252#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001253 /* While in initial call, set default video devices */
1254 if (type == PJMEDIA_TYPE_VIDEO) {
1255 call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev;
1256 call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev;
1257 if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) {
1258 pjmedia_vid_dev_info info;
1259 pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info);
1260 call_med->strm.v.rdr_dev = info.id;
1261 }
1262 if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
1263 pjmedia_vid_dev_info info;
1264 pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info);
1265 call_med->strm.v.cap_dev = info.id;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001266 }
1267 }
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00001268#endif
1269
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001270 } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) {
1271 /* Media is being reenabled. */
1272 call_med->tp_st = PJSUA_MED_TP_INIT;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001273 }
1274
Benny Prijono0bc99a92011-03-17 04:34:43 +00001275#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1276 /* This function may be called when SRTP transport already exists
1277 * (e.g: in re-invite, update), don't need to destroy/re-create.
1278 */
1279 if (!call_med->tp_orig || call_med->tp == call_med->tp_orig) {
1280 pjmedia_srtp_setting srtp_opt;
1281 pjmedia_transport *srtp = NULL;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001282
Benny Prijono0bc99a92011-03-17 04:34:43 +00001283 /* Check if SRTP requires secure signaling */
1284 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
1285 if (security_level < acc->cfg.srtp_secure_signaling) {
1286 if (sip_err_code)
1287 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1288 status = PJSIP_ESESSIONINSECURE;
1289 goto on_error;
1290 }
1291 }
1292
1293 /* Always create SRTP adapter */
1294 pjmedia_srtp_setting_default(&srtp_opt);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001295 srtp_opt.close_member_tp = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001296 /* If media session has been ever established, let's use remote's
1297 * preference in SRTP usage policy, especially when it is stricter.
1298 */
1299 if (call_med->rem_srtp_use > acc->cfg.use_srtp)
1300 srtp_opt.use = call_med->rem_srtp_use;
1301 else
1302 srtp_opt.use = acc->cfg.use_srtp;
1303
1304 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
1305 call_med->tp,
1306 &srtp_opt, &srtp);
1307 if (status != PJ_SUCCESS) {
1308 if (sip_err_code)
1309 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
1310 goto on_error;
1311 }
1312
1313 /* Set SRTP as current media transport */
1314 call_med->tp_orig = call_med->tp;
1315 call_med->tp = srtp;
1316 }
1317#else
Benny Prijono7df19342011-07-23 02:54:03 +00001318 call_med->tp_orig = call_med->tp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001319 PJ_UNUSED_ARG(security_level);
1320#endif
1321
Benny Prijono1fe04ee2011-07-15 07:27:05 +00001322 pjmedia_event_subscription_init(&call_med->esub_rend, &call_media_on_event,
Benny Prijonoee0ba182011-07-15 06:18:29 +00001323 call_med);
Benny Prijono1fe04ee2011-07-15 07:27:05 +00001324 pjmedia_event_subscription_init(&call_med->esub_cap, &call_media_on_event,
1325 call_med);
Benny Prijonoee0ba182011-07-15 06:18:29 +00001326
Benny Prijono0bc99a92011-03-17 04:34:43 +00001327 return PJ_SUCCESS;
1328
1329on_error:
1330 if (call_med->tp) {
1331 pjmedia_transport_close(call_med->tp);
1332 call_med->tp = NULL;
1333 }
1334 return status;
1335}
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001336
Benny Prijonod8179652008-01-23 20:39:07 +00001337pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
1338 pjsip_role_e role,
1339 int security_level,
1340 pj_pool_t *tmp_pool,
1341 const pjmedia_sdp_session *rem_sdp,
1342 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001343{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001344 const pj_str_t STR_AUDIO = { "audio", 5 };
1345 const pj_str_t STR_VIDEO = { "video", 5 };
Benny Prijonod8179652008-01-23 20:39:07 +00001346 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijonod8179652008-01-23 20:39:07 +00001347 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001348 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1349 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
1350 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
1351 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
1352 pjmedia_type media_types[PJSUA_MAX_CALL_MEDIA];
1353 unsigned mi;
1354 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001355
Benny Prijonod8179652008-01-23 20:39:07 +00001356 PJ_UNUSED_ARG(role);
1357
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001358 /*
1359 * Note: this function may be called when the media already exists
1360 * (e.g. in reinvites, updates, etc).
1361 */
1362
Benny Prijono0bc99a92011-03-17 04:34:43 +00001363 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
1364 return PJ_EBUSY;
1365
1366#if DISABLED_FOR_TICKET_1185
Benny Prijonod8179652008-01-23 20:39:07 +00001367 /* Return error if media transport has not been created yet
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001368 * (e.g. application is starting)
1369 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001370 for (i=0; i<call->med_cnt; ++i) {
1371 if (call->media[i].tp == NULL) {
1372 return PJ_EBUSY;
Benny Prijonod8179652008-01-23 20:39:07 +00001373 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001374 }
Benny Prijonod8179652008-01-23 20:39:07 +00001375#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +00001376
Benny Prijono0bc99a92011-03-17 04:34:43 +00001377 if (rem_sdp) {
1378 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
1379 maudidx, &maudcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001380 // Don't apply media count limitation until SDP negotiation is done.
1381 //if (maudcnt > acc->cfg.max_audio_cnt)
1382 // maudcnt = acc->cfg.max_audio_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001383
Benny Prijono0bc99a92011-03-17 04:34:43 +00001384 if (maudcnt==0) {
1385 /* Expecting audio in the offer */
1386 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1387 pjsua_media_channel_deinit(call_id);
1388 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001389 }
1390
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001391#if PJMEDIA_HAS_VIDEO
Benny Prijono0bc99a92011-03-17 04:34:43 +00001392 sort_media(rem_sdp, &STR_VIDEO, acc->cfg.use_srtp,
1393 mvididx, &mvidcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001394 // Don't apply media count limitation until SDP negotiation is done.
1395 //if (mvidcnt > acc->cfg.max_video_cnt)
1396 //mvidcnt = acc->cfg.max_video_cnt;
1397#else
1398 mvidcnt = 0;
1399#endif
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001400
1401 /* Update media count only when remote add any media, this media count
1402 * must never decrease.
Benny Prijonod8179652008-01-23 20:39:07 +00001403 */
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001404 if (call->med_cnt < rem_sdp->media_count)
1405 call->med_cnt = PJ_MIN(rem_sdp->media_count, PJSUA_MAX_CALL_MEDIA);
Benny Prijonod8179652008-01-23 20:39:07 +00001406
Benny Prijono0bc99a92011-03-17 04:34:43 +00001407 } else {
1408 maudcnt = acc->cfg.max_audio_cnt;
1409 for (mi=0; mi<maudcnt; ++mi) {
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001410 maudidx[mi] = (pj_uint8_t)mi;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001411 media_types[mi] = PJMEDIA_TYPE_AUDIO;
Benny Prijonod8179652008-01-23 20:39:07 +00001412 }
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001413#if PJMEDIA_HAS_VIDEO
Benny Prijono0bc99a92011-03-17 04:34:43 +00001414 mvidcnt = acc->cfg.max_video_cnt;
1415 for (mi=0; mi<mvidcnt; ++mi) {
1416 media_types[maudcnt + mi] = PJMEDIA_TYPE_VIDEO;
1417 }
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001418#else
1419 mvidcnt = 0;
1420#endif
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001421 call->med_cnt = maudcnt + mvidcnt;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001422 }
1423
Benny Prijono0bc99a92011-03-17 04:34:43 +00001424 if (call->med_cnt == 0) {
1425 /* Expecting at least one media */
Benny Prijonoab8dba92008-06-27 21:59:15 +00001426 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001427 pjsua_media_channel_deinit(call_id);
Benny Prijonoab8dba92008-06-27 21:59:15 +00001428 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001429 }
1430
Benny Prijono0bc99a92011-03-17 04:34:43 +00001431 /* Initialize each media line */
1432 for (mi=0; mi < call->med_cnt; ++mi) {
1433 pjsua_call_media *call_med = &call->media[mi];
1434 pj_bool_t enabled = PJ_FALSE;
1435 pjmedia_type media_type = PJMEDIA_TYPE_NONE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001436
Benny Prijono0bc99a92011-03-17 04:34:43 +00001437 if (rem_sdp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001438 if (mi >= rem_sdp->media_count) {
1439 /* Media has been removed in remote re-offer */
1440 media_type = call_med->type;
1441 } else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_AUDIO)) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001442 media_type = PJMEDIA_TYPE_AUDIO;
1443 if (pj_memchr(maudidx, mi, maudcnt * sizeof(maudidx[0]))) {
1444 enabled = PJ_TRUE;
1445 }
1446 }
1447 else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_VIDEO)) {
1448 media_type = PJMEDIA_TYPE_VIDEO;
1449 if (pj_memchr(mvididx, mi, mvidcnt * sizeof(mvididx[0]))) {
1450 enabled = PJ_TRUE;
1451 }
1452 }
1453
1454 } else {
1455 enabled = PJ_TRUE;
1456 media_type = media_types[mi];
1457 }
1458
1459 if (enabled) {
1460 status = pjsua_call_media_init(call_med, media_type,
1461 &acc->cfg.rtp_cfg,
1462 security_level, sip_err_code);
1463 if (status != PJ_SUCCESS) {
1464 pjsua_media_channel_deinit(call_id);
1465 return status;
1466 }
1467 } else {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001468 /* By convention, the media is disabled if transport is NULL
1469 * or transport state is PJSUA_MED_TP_DISABLED.
1470 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001471 if (call_med->tp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001472 // Don't close transport here, as SDP negotiation has not been
1473 // done and stream may be still active.
1474 //pjmedia_transport_close(call_med->tp);
1475 //call_med->tp = NULL;
1476 pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT ||
1477 call_med->tp_st == PJSUA_MED_TP_RUNNING);
1478 call_med->tp_st = PJSUA_MED_TP_DISABLED;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001479 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001480
1481 /* Put media type just for info */
1482 call_med->type = media_type;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001483 }
Benny Prijono224b4e22008-06-19 14:10:28 +00001484 }
1485
Benny Prijono0bc99a92011-03-17 04:34:43 +00001486 call->audio_idx = maudidx[0];
1487
1488 PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d",
1489 call->audio_idx, call->index));
1490
1491 /* Tell the media transport of a new offer/answer session */
1492 for (mi=0; mi < call->med_cnt; ++mi) {
1493 pjsua_call_media *call_med = &call->media[mi];
1494
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001495 /* Note: tp may be NULL if this media line is disabled */
1496 if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001497 status = pjmedia_transport_media_create(call_med->tp,
1498 tmp_pool, 0,
1499 rem_sdp, mi);
1500 if (status != PJ_SUCCESS) {
1501 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1502 pjsua_media_channel_deinit(call_id);
1503 return status;
1504 }
1505
1506 call_med->tp_st = PJSUA_MED_TP_INIT;
1507 }
1508 }
1509
Benny Prijonoc97608e2007-03-23 16:34:20 +00001510 return PJ_SUCCESS;
1511}
1512
1513pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1514 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001515 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001516 pjmedia_sdp_session **p_sdp,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001517 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001518{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001519 enum { MAX_MEDIA = PJSUA_MAX_CALL_MEDIA };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001520 pjmedia_sdp_session *sdp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001521 pj_sockaddr origin;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001522 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001523 pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001524 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001525 pj_status_t status;
1526
Benny Prijono0bc99a92011-03-17 04:34:43 +00001527 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
Benny Prijono55e82352007-05-10 20:49:08 +00001528 return PJ_EBUSY;
Benny Prijono55e82352007-05-10 20:49:08 +00001529
Benny Prijono0bc99a92011-03-17 04:34:43 +00001530 if (rem_sdp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001531 /* If this is a re-offer, let's re-initialize media as remote may
1532 * add or remove media
1533 */
1534 if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) {
1535 status = pjsua_media_channel_init(call_id, PJSIP_ROLE_UAS,
1536 call->secure_level, pool,
1537 rem_sdp, sip_err_code);
1538 if (status != PJ_SUCCESS)
1539 return status;
Benny Prijono0324ba52010-12-02 10:41:46 +00001540 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001541
1542#if 0
1543 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001544 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1545 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001546
Benny Prijono0bc99a92011-03-17 04:34:43 +00001547 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
1548 maudidx, &maudcnt);
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001549
Benny Prijono0bc99a92011-03-17 04:34:43 +00001550 if (maudcnt==0) {
1551 /* Expecting audio in the offer */
1552 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1553 pjsua_media_channel_deinit(call_id);
1554 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijono224b4e22008-06-19 14:10:28 +00001555 }
Benny Prijono224b4e22008-06-19 14:10:28 +00001556
Benny Prijono0bc99a92011-03-17 04:34:43 +00001557 call->audio_idx = maudidx[0];
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001558#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00001559 } else {
1560 /* Audio is first in our offer, by convention */
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001561 // The audio_idx should not be changed here, as this function may be
1562 // called in generating re-offer and the current active audio index
1563 // can be anywhere.
1564 //call->audio_idx = 0;
Benny Prijono224b4e22008-06-19 14:10:28 +00001565 }
1566
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001567#if 0
1568 // Since r3512, old-style hold should have got transport, created by
1569 // pjsua_media_channel_init() in initial offer/answer or remote reoffer.
Benny Prijono224b4e22008-06-19 14:10:28 +00001570 /* Create media if it's not created. This could happen when call is
Benny Prijono0bc99a92011-03-17 04:34:43 +00001571 * currently on-hold (with the old style hold)
Benny Prijono224b4e22008-06-19 14:10:28 +00001572 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001573 if (call->media[call->audio_idx].tp == NULL) {
Benny Prijono224b4e22008-06-19 14:10:28 +00001574 pjsip_role_e role;
1575 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1576 status = pjsua_media_channel_init(call_id, role, call->secure_level,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001577 pool, rem_sdp, sip_err_code);
Benny Prijono224b4e22008-06-19 14:10:28 +00001578 if (status != PJ_SUCCESS)
1579 return status;
1580 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001581#endif
Benny Prijono224b4e22008-06-19 14:10:28 +00001582
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001583 /* Get SDP negotiator state */
1584 if (call->inv && call->inv->neg)
1585 sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg);
1586
Benny Prijono0bc99a92011-03-17 04:34:43 +00001587 /* Get one address to use in the origin field */
1588 pj_bzero(&origin, sizeof(origin));
1589 for (mi=0; mi<call->med_cnt; ++mi) {
1590 pjmedia_transport_info tpinfo;
Benny Prijono617c5bc2007-04-02 19:51:21 +00001591
Benny Prijono0bc99a92011-03-17 04:34:43 +00001592 if (call->media[mi].tp == NULL)
1593 continue;
1594
1595 pjmedia_transport_info_init(&tpinfo);
1596 pjmedia_transport_get_info(call->media[mi].tp, &tpinfo);
1597 pj_sockaddr_cp(&origin, &tpinfo.sock_info.rtp_addr_name);
1598 break;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001599 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001600
Benny Prijono0bc99a92011-03-17 04:34:43 +00001601 /* Create the base (blank) SDP */
1602 status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, pool, NULL,
1603 &origin, &sdp);
1604 if (status != PJ_SUCCESS)
1605 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001606
Benny Prijono0bc99a92011-03-17 04:34:43 +00001607 /* Process each media line */
1608 for (mi=0; mi<call->med_cnt; ++mi) {
1609 pjsua_call_media *call_med = &call->media[mi];
1610 pjmedia_sdp_media *m = NULL;
1611 pjmedia_transport_info tpinfo;
1612
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001613 if (rem_sdp && mi >= rem_sdp->media_count) {
1614 /* Remote might have removed some media lines. */
1615 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001616 }
1617
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001618 if (call_med->tp == NULL || call_med->tp_st == PJSUA_MED_TP_DISABLED)
1619 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001620 /*
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001621 * This media is disabled. Just create a valid SDP with zero
Benny Prijono0bc99a92011-03-17 04:34:43 +00001622 * port.
1623 */
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001624 if (rem_sdp) {
1625 /* Just clone the remote media and deactivate it */
1626 m = pjmedia_sdp_media_clone_deactivate(pool,
1627 rem_sdp->media[mi]);
1628 } else {
1629 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1630 m->desc.transport = pj_str("RTP/AVP");
1631 m->desc.fmt_count = 1;
1632 m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
1633 m->conn->net_type = pj_str("IN");
1634 m->conn->addr_type = pj_str("IP4");
1635 m->conn->addr = pj_str("127.0.0.1");
Benny Prijonoa310bd22008-06-27 21:19:44 +00001636
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001637 switch (call_med->type) {
1638 case PJMEDIA_TYPE_AUDIO:
1639 m->desc.media = pj_str("audio");
1640 m->desc.fmt[0] = pj_str("0");
1641 break;
1642 case PJMEDIA_TYPE_VIDEO:
1643 m->desc.media = pj_str("video");
1644 m->desc.fmt[0] = pj_str("31");
1645 break;
1646 default:
1647 if (rem_sdp) {
1648 pj_strdup(pool, &m->desc.media,
1649 &rem_sdp->media[mi]->desc.media);
1650 pj_strdup(pool, &m->desc.fmt[0],
1651 &rem_sdp->media[mi]->desc.fmt[0]);
1652 } else {
1653 pj_assert(!"Invalid call_med media type");
1654 return PJ_EBUG;
1655 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001656 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001657 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001658
1659 sdp->media[sdp->media_count++] = m;
1660 continue;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001661 }
1662
Benny Prijono0bc99a92011-03-17 04:34:43 +00001663 /* Get transport address info */
1664 pjmedia_transport_info_init(&tpinfo);
1665 pjmedia_transport_get_info(call_med->tp, &tpinfo);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001666
Benny Prijono0bc99a92011-03-17 04:34:43 +00001667 /* Ask pjmedia endpoint to create SDP media line */
1668 switch (call_med->type) {
1669 case PJMEDIA_TYPE_AUDIO:
1670 status = pjmedia_endpt_create_audio_sdp(pjsua_var.med_endpt, pool,
1671 &tpinfo.sock_info, 0, &m);
1672 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00001673#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00001674 case PJMEDIA_TYPE_VIDEO:
1675 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
1676 &tpinfo.sock_info, 0, &m);
1677 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00001678#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00001679 default:
1680 pj_assert(!"Invalid call_med media type");
1681 return PJ_EBUG;
1682 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001683
Benny Prijono0bc99a92011-03-17 04:34:43 +00001684 if (status != PJ_SUCCESS)
1685 return status;
1686
1687 sdp->media[sdp->media_count++] = m;
1688
1689 /* Give to transport */
1690 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
1691 sdp, rem_sdp, mi);
1692 if (status != PJ_SUCCESS) {
1693 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1694 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001695 }
Nanang Izzuddin91ba2a22011-04-11 19:59:09 +00001696
1697 /* Copy c= line of the first media to session level,
1698 * if there's none.
1699 */
1700 if (sdp->conn == NULL) {
1701 sdp->conn = pjmedia_sdp_conn_clone(pool, m->conn);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001702 }
1703 }
1704
Benny Prijono6ba8c542007-10-16 01:34:14 +00001705 /* Add NAT info in the SDP */
1706 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1707 pjmedia_sdp_attr *a;
1708 pj_str_t value;
1709 char nat_info[80];
1710
1711 value.ptr = nat_info;
1712 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1713 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1714 "%d", pjsua_var.nat_type);
1715 } else {
1716 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1717 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1718 "%d %s",
1719 pjsua_var.nat_type,
1720 type_name);
1721 }
1722
1723 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1724
1725 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1726
1727 }
1728
Benny Prijonoc97608e2007-03-23 16:34:20 +00001729
Benny Prijono0bc99a92011-03-17 04:34:43 +00001730#if DISABLED_FOR_TICKET_1185 && defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001731 /* Check if SRTP is in optional mode and configured to use duplicated
1732 * media, i.e: secured and unsecured version, in the SDP offer.
1733 */
1734 if (!rem_sdp &&
1735 pjsua_var.acc[call->acc_id].cfg.use_srtp == PJMEDIA_SRTP_OPTIONAL &&
1736 pjsua_var.acc[call->acc_id].cfg.srtp_optional_dup_offer)
1737 {
1738 unsigned i;
1739
1740 for (i = 0; i < sdp->media_count; ++i) {
1741 pjmedia_sdp_media *m = sdp->media[i];
1742
Benny Prijono0bc99a92011-03-17 04:34:43 +00001743 /* Check if this media is unsecured but has SDP "crypto"
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001744 * attribute.
1745 */
1746 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") == 0 &&
1747 pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL)
1748 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001749 if (i == (unsigned)call->audio_idx &&
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001750 sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE)
1751 {
1752 /* This is a session update, and peer has chosen the
1753 * unsecured version, so let's make this unsecured too.
1754 */
1755 pjmedia_sdp_media_remove_all_attr(m, "crypto");
1756 } else {
1757 /* This is new offer, duplicate media so we'll have
1758 * secured (with "RTP/SAVP" transport) and and unsecured
1759 * versions.
1760 */
1761 pjmedia_sdp_media *new_m;
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001762
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001763 /* Duplicate this media and apply secured transport */
1764 new_m = pjmedia_sdp_media_clone(pool, m);
1765 pj_strdup2(pool, &new_m->desc.transport, "RTP/SAVP");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001766
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001767 /* Remove the "crypto" attribute in the unsecured media */
1768 pjmedia_sdp_media_remove_all_attr(m, "crypto");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001769
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001770 /* Insert the new media before the unsecured media */
1771 if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001772 pj_array_insert(sdp->media, sizeof(new_m),
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001773 sdp->media_count, i, &new_m);
1774 ++sdp->media_count;
1775 ++i;
1776 }
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001777 }
1778 }
1779 }
1780 }
1781#endif
1782
Benny Prijonoc97608e2007-03-23 16:34:20 +00001783 *p_sdp = sdp;
1784 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001785}
1786
1787
1788static void stop_media_session(pjsua_call_id call_id)
1789{
1790 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001791 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001792
Benny Prijono0bc99a92011-03-17 04:34:43 +00001793 for (mi=0; mi<call->med_cnt; ++mi) {
1794 pjsua_call_media *call_med = &call->media[mi];
1795
1796 if (call_med->type == PJMEDIA_TYPE_AUDIO) {
1797 pjmedia_stream *strm = call_med->strm.a.stream;
1798 pjmedia_rtcp_stat stat;
1799
1800 if (strm) {
1801 if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) {
1802 if (pjsua_var.mconf) {
1803 pjsua_conf_remove_port(call_med->strm.a.conf_slot);
1804 }
1805 call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
1806 }
1807
1808 if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
1809 (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS))
1810 {
1811 /* Save RTP timestamp & sequence, so when media session is
1812 * restarted, those values will be restored as the initial
1813 * RTP timestamp & sequence of the new media session. So in
1814 * the same call session, RTP timestamp and sequence are
1815 * guaranteed to be contigue.
1816 */
1817 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
1818 call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
1819 call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
1820 }
1821
1822 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1823 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, strm, mi);
1824 }
1825
1826 pjmedia_stream_destroy(strm);
1827 call_med->strm.a.stream = NULL;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001828 }
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001829 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00001830
1831#if PJMEDIA_HAS_VIDEO
1832 else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
Nanang Izzuddin62053a62011-07-12 11:08:32 +00001833 stop_video_stream(call_med);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001834 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00001835#endif
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001836
1837 PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed",
1838 call_id, mi));
Benny Prijono0bc99a92011-03-17 04:34:43 +00001839 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001840 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001841}
1842
1843pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1844{
1845 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001846 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001847
1848 stop_media_session(call_id);
1849
Benny Prijono0bc99a92011-03-17 04:34:43 +00001850 for (mi=0; mi<call->med_cnt; ++mi) {
1851 pjsua_call_media *call_med = &call->media[mi];
Benny Prijonoc97608e2007-03-23 16:34:20 +00001852
Benny Prijono0bc99a92011-03-17 04:34:43 +00001853 if (call_med->tp_st != PJSUA_MED_TP_IDLE) {
1854 pjmedia_transport_media_stop(call_med->tp);
1855 call_med->tp_st = PJSUA_MED_TP_IDLE;
1856 }
1857
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001858 //if (call_med->tp_orig && call_med->tp &&
1859 // call_med->tp != call_med->tp_orig)
1860 //{
1861 // pjmedia_transport_close(call_med->tp);
1862 // call_med->tp = call_med->tp_orig;
1863 //}
1864 if (call_med->tp) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001865 pjmedia_transport_close(call_med->tp);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001866 call_med->tp = call_med->tp_orig = NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001867 }
Benny Prijonod8179652008-01-23 20:39:07 +00001868 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00001869
1870 check_snd_dev_idle();
1871
Benny Prijonoc97608e2007-03-23 16:34:20 +00001872 return PJ_SUCCESS;
1873}
1874
1875
1876/*
1877 * DTMF callback from the stream.
1878 */
1879static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1880 int digit)
1881{
1882 PJ_UNUSED_ARG(strm);
1883
Benny Prijono0c068262008-02-14 14:38:52 +00001884 /* For discussions about call mutex protection related to this
1885 * callback, please see ticket #460:
1886 * http://trac.pjsip.org/repos/ticket/460#comment:4
1887 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001888 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1889 pjsua_call_id call_id;
1890
Benny Prijonod8179652008-01-23 20:39:07 +00001891 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001892 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1893 }
1894}
1895
1896
Benny Prijono0bc99a92011-03-17 04:34:43 +00001897static pj_status_t audio_channel_update(pjsua_call_media *call_med,
1898 pj_pool_t *tmp_pool,
1899 const pjmedia_sdp_session *local_sdp,
1900 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001901{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001902 pjsua_call *call = call_med->call;
1903 pjmedia_stream_info the_si, *si = &the_si;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001904 pjmedia_port *media_port;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001905 unsigned strm_idx = call_med->idx;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001906 pj_status_t status;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001907
1908 status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
1909 local_sdp, remote_sdp, strm_idx);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001910 if (status != PJ_SUCCESS)
1911 return status;
1912
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001913 /* Check if no media is active */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001914 if (si->dir == PJMEDIA_DIR_NONE) {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001915 /* Call media state */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001916 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001917
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001918 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001919 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001920
Benny Prijonoc97608e2007-03-23 16:34:20 +00001921 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001922 pjmedia_transport_info tp_info;
1923
Benny Prijono224b4e22008-06-19 14:10:28 +00001924 /* Start/restart media transport */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001925 status = pjmedia_transport_media_start(call_med->tp,
1926 tmp_pool, local_sdp,
1927 remote_sdp, strm_idx);
Benny Prijonod8179652008-01-23 20:39:07 +00001928 if (status != PJ_SUCCESS)
1929 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001930
Benny Prijono0bc99a92011-03-17 04:34:43 +00001931 call_med->tp_st = PJSUA_MED_TP_RUNNING;
Benny Prijono224b4e22008-06-19 14:10:28 +00001932
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001933 /* Get remote SRTP usage policy */
1934 pjmedia_transport_info_init(&tp_info);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001935 pjmedia_transport_get_info(call_med->tp, &tp_info);
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001936 if (tp_info.specific_info_cnt > 0) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +00001937 unsigned i;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001938 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1939 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1940 {
1941 pjmedia_srtp_info *srtp_info =
1942 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1943
Benny Prijono0bc99a92011-03-17 04:34:43 +00001944 call_med->rem_srtp_use = srtp_info->peer_use;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001945 break;
1946 }
1947 }
1948 }
1949
Benny Prijonoc97608e2007-03-23 16:34:20 +00001950 /* Override ptime, if this option is specified. */
1951 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001952 si->param->setting.frm_per_pkt = (pj_uint8_t)
1953 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1954 if (si->param->setting.frm_per_pkt == 0)
1955 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001956 }
1957
1958 /* Disable VAD, if this option is specified. */
1959 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001960 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001961 }
1962
1963
1964 /* Optionally, application may modify other stream settings here
1965 * (such as jitter buffer parameters, codec ptime, etc.)
1966 */
Benny Prijono91e567e2007-12-28 08:51:58 +00001967 si->jb_init = pjsua_var.media_cfg.jb_init;
1968 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1969 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1970 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001971
Benny Prijono8147f402007-11-21 14:50:07 +00001972 /* Set SSRC */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001973 si->ssrc = call_med->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00001974
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001975 /* Set RTP timestamp & sequence, normally these value are intialized
1976 * automatically when stream session created, but for some cases (e.g:
1977 * call reinvite, call update) timestamp and sequence need to be kept
1978 * contigue.
1979 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001980 si->rtp_ts = call_med->rtp_tx_ts;
1981 si->rtp_seq = call_med->rtp_tx_seq;
1982 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001983
Nanang Izzuddin5e39a2b2010-09-20 06:13:02 +00001984#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
1985 /* Enable/disable stream keep-alive and NAT hole punch. */
1986 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
1987#endif
1988
Benny Prijonoc97608e2007-03-23 16:34:20 +00001989 /* Create session based on session info. */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001990 status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,
1991 call_med->tp, NULL,
1992 &call_med->strm.a.stream);
1993 if (status != PJ_SUCCESS) {
1994 return status;
1995 }
1996
1997 /* Start stream */
1998 status = pjmedia_stream_start(call_med->strm.a.stream);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001999 if (status != PJ_SUCCESS) {
2000 return status;
2001 }
2002
2003 /* If DTMF callback is installed by application, install our
2004 * callback to the session.
2005 */
2006 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002007 pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
2008 &dtmf_callback,
2009 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00002010 }
2011
2012 /* Get the port interface of the first stream in the session.
2013 * We need the port interface to add to the conference bridge.
2014 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002015 pjmedia_stream_get_port(call_med->strm.a.stream, &media_port);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002016
Benny Prijonofc13bf62008-02-20 08:56:15 +00002017 /* Notify application about stream creation.
2018 * Note: application may modify media_port to point to different
2019 * media port
2020 */
2021 if (pjsua_var.ua_cfg.cb.on_stream_created) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002022 pjsua_var.ua_cfg.cb.on_stream_created(call->index,
2023 call_med->strm.a.stream,
2024 strm_idx, &media_port);
Benny Prijonofc13bf62008-02-20 08:56:15 +00002025 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002026
2027 /*
2028 * Add the call to conference bridge.
2029 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002030 {
2031 char tmp[PJSIP_MAX_URL_SIZE];
2032 pj_str_t port_name;
2033
2034 port_name.ptr = tmp;
2035 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
2036 call->inv->dlg->remote.info->uri,
2037 tmp, sizeof(tmp));
2038 if (port_name.slen < 1) {
2039 port_name = pj_str("call");
2040 }
Benny Prijono40d62b62009-08-12 17:53:47 +00002041 status = pjmedia_conf_add_port( pjsua_var.mconf,
2042 call->inv->pool_prov,
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002043 media_port,
2044 &port_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +00002045 (unsigned*)
2046 &call_med->strm.a.conf_slot);
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002047 if (status != PJ_SUCCESS) {
2048 return status;
2049 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002050 }
2051
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002052 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002053 call_med->dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002054
2055 /* Call media state */
2056 if (call->local_hold)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002057 call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
2058 else if (call_med->dir == PJMEDIA_DIR_DECODING)
2059 call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002060 else
Benny Prijono0bc99a92011-03-17 04:34:43 +00002061 call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002062 }
2063
2064 /* Print info. */
2065 {
2066 char info[80];
2067 int info_len = 0;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002068 int len;
2069 const char *dir;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002070
Benny Prijono0bc99a92011-03-17 04:34:43 +00002071 switch (si->dir) {
2072 case PJMEDIA_DIR_NONE:
2073 dir = "inactive";
2074 break;
2075 case PJMEDIA_DIR_ENCODING:
2076 dir = "sendonly";
2077 break;
2078 case PJMEDIA_DIR_DECODING:
2079 dir = "recvonly";
2080 break;
2081 case PJMEDIA_DIR_ENCODING_DECODING:
2082 dir = "sendrecv";
2083 break;
2084 default:
2085 dir = "unknown";
2086 break;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002087 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00002088 len = pj_ansi_sprintf( info+info_len,
2089 ", stream #%d: %.*s (%s)", strm_idx,
2090 (int)si->fmt.encoding_name.slen,
2091 si->fmt.encoding_name.ptr,
2092 dir);
2093 if (len > 0)
2094 info_len += len;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002095 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
2096 }
2097
2098 return PJ_SUCCESS;
2099}
2100
Benny Prijono0bc99a92011-03-17 04:34:43 +00002101pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
2102 const pjmedia_sdp_session *local_sdp,
2103 const pjmedia_sdp_session *remote_sdp)
2104{
2105 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002106 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002107 pj_pool_t *tmp_pool = call->inv->pool_prov;
2108 unsigned mi;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002109 pj_bool_t got_media = PJ_FALSE;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002110 pj_status_t status = PJ_SUCCESS;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002111
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002112 const pj_str_t STR_AUDIO = { "audio", 5 };
2113 const pj_str_t STR_VIDEO = { "video", 5 };
2114 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
2115 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
2116 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
2117 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
2118 pj_bool_t need_renego_sdp = PJ_FALSE;
2119
Benny Prijono0bc99a92011-03-17 04:34:43 +00002120 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
2121 return PJ_EBUSY;
2122
2123 /* Destroy existing media session, if any. */
2124 stop_media_session(call->index);
2125
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002126 /* Call media count must be at least equal to SDP media. Note that
2127 * it may not be equal when remote removed any SDP media line.
2128 */
2129 pj_assert(call->med_cnt >= local_sdp->media_count);
2130
Benny Prijono0bc99a92011-03-17 04:34:43 +00002131 /* Reset audio_idx first */
2132 call->audio_idx = -1;
2133
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002134 /* Apply maximum audio/video count of the account */
2135 sort_media(local_sdp, &STR_AUDIO, acc->cfg.use_srtp,
2136 maudidx, &maudcnt);
2137#if PJMEDIA_HAS_VIDEO
2138 sort_media(local_sdp, &STR_VIDEO, acc->cfg.use_srtp,
2139 mvididx, &mvidcnt);
2140#else
2141 PJ_UNUSED_ARG(STR_VIDEO);
2142 mvidcnt = 0;
2143#endif
2144 if (maudcnt > acc->cfg.max_audio_cnt || mvidcnt > acc->cfg.max_video_cnt)
2145 {
2146 pjmedia_sdp_session *local_sdp2;
2147
2148 maudcnt = PJ_MIN(maudcnt, acc->cfg.max_audio_cnt);
2149 mvidcnt = PJ_MIN(mvidcnt, acc->cfg.max_video_cnt);
2150 local_sdp2 = pjmedia_sdp_session_clone(tmp_pool, local_sdp);
2151
2152 for (mi=0; mi < local_sdp2->media_count; ++mi) {
2153 pjmedia_sdp_media *m = local_sdp2->media[mi];
2154
2155 if (m->desc.port == 0 ||
2156 pj_memchr(maudidx, mi, maudcnt*sizeof(maudidx[0])) ||
2157 pj_memchr(mvididx, mi, mvidcnt*sizeof(mvididx[0])))
2158 {
2159 continue;
2160 }
2161
2162 /* Deactivate this media */
2163 pjmedia_sdp_media_deactivate(tmp_pool, m);
2164 }
2165
2166 local_sdp = local_sdp2;
2167 need_renego_sdp = PJ_TRUE;
2168 }
2169
Benny Prijono0bc99a92011-03-17 04:34:43 +00002170 /* Process each media stream */
2171 for (mi=0; mi < call->med_cnt; ++mi) {
2172 pjsua_call_media *call_med = &call->media[mi];
2173
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002174 if (mi >= local_sdp->media_count ||
2175 mi >= remote_sdp->media_count)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002176 {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002177 /* This may happen when remote removed any SDP media lines in
2178 * its re-offer.
2179 */
2180 continue;
2181#if 0
Benny Prijono0bc99a92011-03-17 04:34:43 +00002182 /* Something is wrong */
2183 PJ_LOG(1,(THIS_FILE, "Error updating media for call %d: "
2184 "invalid media index %d in SDP", call_id, mi));
2185 return PJMEDIA_SDP_EINSDP;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002186#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002187 }
2188
2189 switch (call_med->type) {
2190 case PJMEDIA_TYPE_AUDIO:
2191 status = audio_channel_update(call_med, tmp_pool,
2192 local_sdp, remote_sdp);
2193 if (call->audio_idx==-1 && status==PJ_SUCCESS &&
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002194 call_med->strm.a.stream)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002195 {
2196 call->audio_idx = mi;
2197 }
2198 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00002199#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002200 case PJMEDIA_TYPE_VIDEO:
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002201 status = video_channel_update(call_med, tmp_pool,
2202 local_sdp, remote_sdp);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002203 break;
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002204#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002205 default:
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002206 status = PJMEDIA_EINVALIMEDIATYPE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002207 break;
2208 }
2209
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002210 /* Close the transport of deactivated media, need this here as media
2211 * can be deactivated by the SDP negotiation and the max media count
2212 * (account) setting.
2213 */
2214 if (local_sdp->media[mi]->desc.port==0 && call_med->tp) {
2215 pjmedia_transport_close(call_med->tp);
2216 call_med->tp = call_med->tp_orig = NULL;
2217 call_med->tp_st = PJSUA_MED_TP_IDLE;
2218 }
2219
Benny Prijono0bc99a92011-03-17 04:34:43 +00002220 if (status != PJ_SUCCESS) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002221 PJ_PERROR(1,(THIS_FILE, status, "Error updating media call%02d:%d",
Benny Prijono0bc99a92011-03-17 04:34:43 +00002222 call_id, mi));
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002223 } else {
2224 got_media = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002225 }
2226 }
2227
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002228 /* Perform SDP re-negotiation if needed. */
2229 if (got_media && need_renego_sdp) {
2230 pjmedia_sdp_neg *neg = call->inv->neg;
2231
2232 /* This should only happen when we are the answerer. */
2233 PJ_ASSERT_RETURN(neg && !pjmedia_sdp_neg_was_answer_remote(neg),
2234 PJMEDIA_SDPNEG_EINSTATE);
2235
2236 status = pjmedia_sdp_neg_set_remote_offer(tmp_pool, neg, remote_sdp);
2237 if (status != PJ_SUCCESS)
2238 return status;
2239
2240 status = pjmedia_sdp_neg_set_local_answer(tmp_pool, neg, local_sdp);
2241 if (status != PJ_SUCCESS)
2242 return status;
2243
2244 status = pjmedia_sdp_neg_negotiate(tmp_pool, neg, 0);
2245 if (status != PJ_SUCCESS)
2246 return status;
2247 }
2248
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002249 return (got_media? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002250}
2251
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002252/*
2253 * Get maxinum number of conference ports.
2254 */
2255PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
2256{
2257 return pjsua_var.media_cfg.max_media_ports;
2258}
2259
2260
2261/*
2262 * Get current number of active ports in the bridge.
2263 */
2264PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
2265{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002266 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002267 unsigned count = PJ_ARRAY_SIZE(ports);
2268 pj_status_t status;
2269
2270 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
2271 if (status != PJ_SUCCESS)
2272 count = 0;
2273
2274 return count;
2275}
2276
2277
2278/*
2279 * Enumerate all conference ports.
2280 */
2281PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
2282 unsigned *count)
2283{
2284 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
2285}
2286
2287
2288/*
2289 * Get information about the specified conference port
2290 */
2291PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
2292 pjsua_conf_port_info *info)
2293{
2294 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00002295 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002296 pj_status_t status;
2297
2298 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
2299 if (status != PJ_SUCCESS)
2300 return status;
2301
Benny Prijonoac623b32006-07-03 15:19:31 +00002302 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002303 info->slot_id = id;
2304 info->name = cinfo.name;
2305 info->clock_rate = cinfo.clock_rate;
2306 info->channel_count = cinfo.channel_count;
2307 info->samples_per_frame = cinfo.samples_per_frame;
2308 info->bits_per_sample = cinfo.bits_per_sample;
2309
2310 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00002311 info->listener_cnt = cinfo.listener_cnt;
2312 for (i=0; i<cinfo.listener_cnt; ++i) {
2313 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002314 }
2315
2316 return PJ_SUCCESS;
2317}
2318
2319
2320/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00002321 * Add arbitrary media port to PJSUA's conference bridge.
2322 */
2323PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
2324 pjmedia_port *port,
2325 pjsua_conf_port_id *p_id)
2326{
2327 pj_status_t status;
2328
2329 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
2330 port, NULL, (unsigned*)p_id);
2331 if (status != PJ_SUCCESS) {
2332 if (p_id)
2333 *p_id = PJSUA_INVALID_ID;
2334 }
2335
2336 return status;
2337}
2338
2339
2340/*
2341 * Remove arbitrary slot from the conference bridge.
2342 */
2343PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
2344{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002345 pj_status_t status;
2346
2347 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
2348 check_snd_dev_idle();
2349
2350 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00002351}
2352
2353
2354/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002355 * Establish unidirectional media flow from souce to sink.
2356 */
2357PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
2358 pjsua_conf_port_id sink)
2359{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002360 /* If sound device idle timer is active, cancel it first. */
Benny Prijono0f711b42009-05-06 19:08:43 +00002361 PJSUA_LOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002362 if (pjsua_var.snd_idle_timer.id) {
2363 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
2364 pjsua_var.snd_idle_timer.id = PJ_FALSE;
2365 }
Benny Prijono0f711b42009-05-06 19:08:43 +00002366 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002367
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002368
Benny Prijonof798e502009-03-09 13:08:16 +00002369 /* For audio switchboard (i.e. APS-Direct):
2370 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002371 * (format, clock rate, channel count) must match to peer's.
2372 * Note that sound device can be reopened only if it doesn't have
2373 * any connection.
2374 */
Benny Prijonof798e502009-03-09 13:08:16 +00002375 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002376 pjmedia_conf_port_info port0_info;
2377 pjmedia_conf_port_info peer_info;
2378 unsigned peer_id;
2379 pj_bool_t need_reopen = PJ_FALSE;
2380 pj_status_t status;
2381
2382 peer_id = (source!=0)? source : sink;
2383 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
2384 &peer_info);
2385 pj_assert(status == PJ_SUCCESS);
2386
2387 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
2388 pj_assert(status == PJ_SUCCESS);
2389
2390 /* Check if sound device is instantiated. */
2391 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2392 !pjsua_var.no_snd);
2393
2394 /* Check if sound device need to reopen because it needs to modify
2395 * settings to match its peer. Sound device must be idle in this case
2396 * though.
2397 */
2398 if (!need_reopen &&
2399 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
2400 {
2401 need_reopen = (peer_info.format.id != port0_info.format.id ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002402 peer_info.format.det.aud.avg_bps !=
2403 port0_info.format.det.aud.avg_bps ||
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002404 peer_info.clock_rate != port0_info.clock_rate ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002405 peer_info.channel_count!=port0_info.channel_count);
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002406 }
2407
2408 if (need_reopen) {
Benny Prijonod65f78c2009-06-03 18:59:37 +00002409 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
Sauw Ming98766c72011-03-11 06:57:24 +00002410 pjmedia_snd_port_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002411
Benny Prijonod65f78c2009-06-03 18:59:37 +00002412 /* Create parameter based on peer info */
Sauw Ming98766c72011-03-11 06:57:24 +00002413 status = create_aud_param(&param.base, pjsua_var.cap_dev,
Benny Prijonod65f78c2009-06-03 18:59:37 +00002414 pjsua_var.play_dev,
2415 peer_info.clock_rate,
2416 peer_info.channel_count,
2417 peer_info.samples_per_frame,
2418 peer_info.bits_per_sample);
2419 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002420 pjsua_perror(THIS_FILE, "Error opening sound device",
2421 status);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002422 return status;
2423 }
Benny Prijonof798e502009-03-09 13:08:16 +00002424
Benny Prijonod65f78c2009-06-03 18:59:37 +00002425 /* And peer format */
2426 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
Sauw Ming98766c72011-03-11 06:57:24 +00002427 param.base.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
2428 param.base.ext_fmt = peer_info.format;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002429 }
Benny Prijono10454dc2009-02-21 14:21:59 +00002430
Sauw Ming98766c72011-03-11 06:57:24 +00002431 param.options = 0;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002432 status = open_snd_dev(&param);
2433 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002434 pjsua_perror(THIS_FILE, "Error opening sound device",
2435 status);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002436 return status;
2437 }
2438 } else {
2439 /* Null-audio */
Benny Prijonoc45d9512010-12-10 11:04:30 +00002440 status = pjsua_set_snd_dev(pjsua_var.cap_dev,
2441 pjsua_var.play_dev);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002442 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002443 pjsua_perror(THIS_FILE, "Error opening sound device",
2444 status);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002445 return status;
2446 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002447 }
Benny Prijono2d647722011-07-13 03:05:22 +00002448 } else if (pjsua_var.no_snd) {
2449 if (!pjsua_var.snd_is_on) {
2450 pjsua_var.snd_is_on = PJ_TRUE;
2451 /* Notify app */
2452 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2453 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2454 }
2455 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002456 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002457
Benny Prijonof798e502009-03-09 13:08:16 +00002458 } else {
2459 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002460
Benny Prijonof798e502009-03-09 13:08:16 +00002461 /* Create sound port if none is instantiated */
2462 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2463 !pjsua_var.no_snd)
2464 {
2465 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002466
Benny Prijonof798e502009-03-09 13:08:16 +00002467 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
2468 if (status != PJ_SUCCESS) {
2469 pjsua_perror(THIS_FILE, "Error opening sound device", status);
2470 return status;
2471 }
Benny Prijono2d647722011-07-13 03:05:22 +00002472 } else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) {
2473 pjsua_var.snd_is_on = PJ_TRUE;
2474 /* Notify app */
2475 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2476 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2477 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002478 }
Benny Prijonof798e502009-03-09 13:08:16 +00002479 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002480
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002481 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
2482}
2483
2484
2485/*
2486 * Disconnect media flow from the source to destination port.
2487 */
2488PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
2489 pjsua_conf_port_id sink)
2490{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002491 pj_status_t status;
2492
2493 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002494 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002495
2496 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002497}
2498
2499
Benny Prijono6dd967c2006-12-26 02:27:14 +00002500/*
2501 * Adjust the signal level to be transmitted from the bridge to the
2502 * specified port by making it louder or quieter.
2503 */
2504PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
2505 float level)
2506{
2507 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
2508 (int)((level-1) * 128));
2509}
2510
2511/*
2512 * Adjust the signal level to be received from the specified port (to
2513 * the bridge) by making it louder or quieter.
2514 */
2515PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
2516 float level)
2517{
2518 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
2519 (int)((level-1) * 128));
2520}
2521
2522
2523/*
2524 * Get last signal level transmitted to or received from the specified port.
2525 */
2526PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
2527 unsigned *tx_level,
2528 unsigned *rx_level)
2529{
2530 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
2531 tx_level, rx_level);
2532}
2533
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002534/*****************************************************************************
2535 * File player.
2536 */
2537
Benny Prijonod5696da2007-07-17 16:25:45 +00002538static char* get_basename(const char *path, unsigned len)
2539{
2540 char *p = ((char*)path) + len;
2541
2542 if (len==0)
2543 return p;
2544
Benny Prijono1f61a8f2007-08-16 10:11:44 +00002545 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00002546
2547 return (p==path) ? p : p+1;
2548}
2549
2550
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002551/*
2552 * Create a file player, and automatically connect this player to
2553 * the conference bridge.
2554 */
2555PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
2556 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002557 pjsua_player_id *p_id)
2558{
2559 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002560 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00002561 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002562 pjmedia_port *port;
2563 pj_status_t status;
2564
2565 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2566 return PJ_ETOOMANY;
2567
2568 PJSUA_LOCK();
2569
2570 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2571 if (pjsua_var.player[file_id].port == NULL)
2572 break;
2573 }
2574
2575 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2576 /* This is unexpected */
2577 PJSUA_UNLOCK();
2578 pj_assert(0);
2579 return PJ_EBUG;
2580 }
2581
2582 pj_memcpy(path, filename->ptr, filename->slen);
2583 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00002584
2585 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2586 if (!pool) {
2587 PJSUA_UNLOCK();
2588 return PJ_ENOMEM;
2589 }
2590
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00002591 status = pjmedia_wav_player_port_create(
2592 pool, path,
2593 pjsua_var.mconf_cfg.samples_per_frame *
Benny Prijonoc45d9512010-12-10 11:04:30 +00002594 1000 / pjsua_var.media_cfg.channel_count /
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00002595 pjsua_var.media_cfg.clock_rate,
2596 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002597 if (status != PJ_SUCCESS) {
2598 PJSUA_UNLOCK();
2599 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002600 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002601 return status;
2602 }
2603
Benny Prijono5297af92008-03-18 13:40:40 +00002604 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002605 port, filename, &slot);
2606 if (status != PJ_SUCCESS) {
2607 pjmedia_port_destroy(port);
2608 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00002609 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
2610 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002611 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002612 return status;
2613 }
2614
Benny Prijonoa66c3312007-01-21 23:12:40 +00002615 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00002616 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002617 pjsua_var.player[file_id].port = port;
2618 pjsua_var.player[file_id].slot = slot;
2619
2620 if (p_id) *p_id = file_id;
2621
2622 ++pjsua_var.player_cnt;
2623
2624 PJSUA_UNLOCK();
2625 return PJ_SUCCESS;
2626}
2627
2628
2629/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00002630 * Create a file playlist media port, and automatically add the port
2631 * to the conference bridge.
2632 */
2633PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
2634 unsigned file_count,
2635 const pj_str_t *label,
2636 unsigned options,
2637 pjsua_player_id *p_id)
2638{
2639 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00002640 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002641 pjmedia_port *port;
2642 pj_status_t status;
2643
2644 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2645 return PJ_ETOOMANY;
2646
2647 PJSUA_LOCK();
2648
2649 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2650 if (pjsua_var.player[file_id].port == NULL)
2651 break;
2652 }
2653
2654 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2655 /* This is unexpected */
2656 PJSUA_UNLOCK();
2657 pj_assert(0);
2658 return PJ_EBUG;
2659 }
2660
2661
2662 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
2663 pjsua_var.media_cfg.clock_rate;
2664
Benny Prijonod5696da2007-07-17 16:25:45 +00002665 pool = pjsua_pool_create("playlist", 1000, 1000);
2666 if (!pool) {
2667 PJSUA_UNLOCK();
2668 return PJ_ENOMEM;
2669 }
2670
2671 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002672 file_names, file_count,
2673 ptime, options, 0, &port);
2674 if (status != PJ_SUCCESS) {
2675 PJSUA_UNLOCK();
2676 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002677 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002678 return status;
2679 }
2680
Benny Prijonod5696da2007-07-17 16:25:45 +00002681 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002682 port, &port->info.name, &slot);
2683 if (status != PJ_SUCCESS) {
2684 pjmedia_port_destroy(port);
2685 PJSUA_UNLOCK();
2686 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002687 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002688 return status;
2689 }
2690
2691 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00002692 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002693 pjsua_var.player[file_id].port = port;
2694 pjsua_var.player[file_id].slot = slot;
2695
2696 if (p_id) *p_id = file_id;
2697
2698 ++pjsua_var.player_cnt;
2699
2700 PJSUA_UNLOCK();
2701 return PJ_SUCCESS;
2702
2703}
2704
2705
2706/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002707 * Get conference port ID associated with player.
2708 */
2709PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
2710{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002711 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002712 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2713
2714 return pjsua_var.player[id].slot;
2715}
2716
Benny Prijono469b1522006-12-26 03:05:17 +00002717/*
2718 * Get the media port for the player.
2719 */
Benny Prijonobe41d862008-01-18 13:24:28 +00002720PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00002721 pjmedia_port **p_port)
2722{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002723 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002724 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2725 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2726
2727 *p_port = pjsua_var.player[id].port;
2728
2729 return PJ_SUCCESS;
2730}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002731
2732/*
2733 * Set playback position.
2734 */
2735PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
2736 pj_uint32_t samples)
2737{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002738 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002739 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002740 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002741
2742 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
2743}
2744
2745
2746/*
2747 * Close the file, remove the player from the bridge, and free
2748 * resources associated with the file player.
2749 */
2750PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
2751{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002752 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002753 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2754
2755 PJSUA_LOCK();
2756
2757 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002758 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002759 pjmedia_port_destroy(pjsua_var.player[id].port);
2760 pjsua_var.player[id].port = NULL;
2761 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002762 pj_pool_release(pjsua_var.player[id].pool);
2763 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002764 pjsua_var.player_cnt--;
2765 }
2766
2767 PJSUA_UNLOCK();
2768
2769 return PJ_SUCCESS;
2770}
2771
2772
2773/*****************************************************************************
2774 * File recorder.
2775 */
2776
2777/*
2778 * Create a file recorder, and automatically connect this recorder to
2779 * the conference bridge.
2780 */
2781PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00002782 unsigned enc_type,
2783 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002784 pj_ssize_t max_size,
2785 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002786 pjsua_recorder_id *p_id)
2787{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002788 enum Format
2789 {
2790 FMT_UNKNOWN,
2791 FMT_WAV,
2792 FMT_MP3,
2793 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002794 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002795 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002796 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00002797 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00002798 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002799 pjmedia_port *port;
2800 pj_status_t status;
2801
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002802 /* Filename must present */
2803 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
2804
Benny Prijono00cae612006-07-31 15:19:36 +00002805 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002806 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002807
Benny Prijono8f310522006-10-20 11:08:49 +00002808 /* Don't support encoding type at present */
2809 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002810
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002811 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
2812 return PJ_ETOOMANY;
2813
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002814 /* Determine the file format */
2815 ext.ptr = filename->ptr + filename->slen - 4;
2816 ext.slen = 4;
2817
2818 if (pj_stricmp2(&ext, ".wav") == 0)
2819 file_format = FMT_WAV;
2820 else if (pj_stricmp2(&ext, ".mp3") == 0)
2821 file_format = FMT_MP3;
2822 else {
2823 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
2824 "determine file format for %.*s",
2825 (int)filename->slen, filename->ptr));
2826 return PJ_ENOTSUP;
2827 }
2828
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002829 PJSUA_LOCK();
2830
2831 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
2832 if (pjsua_var.recorder[file_id].port == NULL)
2833 break;
2834 }
2835
2836 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
2837 /* This is unexpected */
2838 PJSUA_UNLOCK();
2839 pj_assert(0);
2840 return PJ_EBUG;
2841 }
2842
2843 pj_memcpy(path, filename->ptr, filename->slen);
2844 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002845
Benny Prijonod5696da2007-07-17 16:25:45 +00002846 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2847 if (!pool) {
2848 PJSUA_UNLOCK();
2849 return PJ_ENOMEM;
2850 }
2851
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002852 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00002853 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002854 pjsua_var.media_cfg.clock_rate,
2855 pjsua_var.mconf_cfg.channel_count,
2856 pjsua_var.mconf_cfg.samples_per_frame,
2857 pjsua_var.mconf_cfg.bits_per_sample,
2858 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002859 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00002860 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002861 port = NULL;
2862 status = PJ_ENOTSUP;
2863 }
2864
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002865 if (status != PJ_SUCCESS) {
2866 PJSUA_UNLOCK();
2867 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002868 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002869 return status;
2870 }
2871
Benny Prijonod5696da2007-07-17 16:25:45 +00002872 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002873 port, filename, &slot);
2874 if (status != PJ_SUCCESS) {
2875 pjmedia_port_destroy(port);
2876 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00002877 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002878 return status;
2879 }
2880
2881 pjsua_var.recorder[file_id].port = port;
2882 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00002883 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002884
2885 if (p_id) *p_id = file_id;
2886
2887 ++pjsua_var.rec_cnt;
2888
2889 PJSUA_UNLOCK();
2890 return PJ_SUCCESS;
2891}
2892
2893
2894/*
2895 * Get conference port associated with recorder.
2896 */
2897PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
2898{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002899 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2900 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002901 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2902
2903 return pjsua_var.recorder[id].slot;
2904}
2905
Benny Prijono469b1522006-12-26 03:05:17 +00002906/*
2907 * Get the media port for the recorder.
2908 */
2909PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
2910 pjmedia_port **p_port)
2911{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002912 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2913 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002914 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2915 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2916
2917 *p_port = pjsua_var.recorder[id].port;
2918 return PJ_SUCCESS;
2919}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002920
2921/*
2922 * Destroy recorder (this will complete recording).
2923 */
2924PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
2925{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002926 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2927 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002928 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2929
2930 PJSUA_LOCK();
2931
2932 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002933 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002934 pjmedia_port_destroy(pjsua_var.recorder[id].port);
2935 pjsua_var.recorder[id].port = NULL;
2936 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002937 pj_pool_release(pjsua_var.recorder[id].pool);
2938 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002939 pjsua_var.rec_cnt--;
2940 }
2941
2942 PJSUA_UNLOCK();
2943
2944 return PJ_SUCCESS;
2945}
2946
2947
2948/*****************************************************************************
2949 * Sound devices.
2950 */
2951
2952/*
2953 * Enum sound devices.
2954 */
Benny Prijonof798e502009-03-09 13:08:16 +00002955
2956PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002957 unsigned *count)
2958{
2959 unsigned i, dev_count;
2960
Benny Prijono10454dc2009-02-21 14:21:59 +00002961 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002962
2963 if (dev_count > *count) dev_count = *count;
2964
2965 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00002966 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002967
Benny Prijono10454dc2009-02-21 14:21:59 +00002968 status = pjmedia_aud_dev_get_info(i, &info[i]);
2969 if (status != PJ_SUCCESS)
2970 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002971 }
2972
2973 *count = dev_count;
2974
2975 return PJ_SUCCESS;
2976}
Benny Prijonof798e502009-03-09 13:08:16 +00002977
2978
Benny Prijono10454dc2009-02-21 14:21:59 +00002979PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
2980 unsigned *count)
2981{
2982 unsigned i, dev_count;
2983
2984 dev_count = pjmedia_aud_dev_count();
2985
2986 if (dev_count > *count) dev_count = *count;
2987 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
2988
2989 for (i=0; i<dev_count; ++i) {
2990 pjmedia_aud_dev_info ai;
2991 pj_status_t status;
2992
2993 status = pjmedia_aud_dev_get_info(i, &ai);
2994 if (status != PJ_SUCCESS)
2995 return status;
2996
2997 strncpy(info[i].name, ai.name, sizeof(info[i].name));
2998 info[i].name[sizeof(info[i].name)-1] = '\0';
2999 info[i].input_count = ai.input_count;
3000 info[i].output_count = ai.output_count;
3001 info[i].default_samples_per_sec = ai.default_samples_per_sec;
3002 }
3003
3004 *count = dev_count;
3005
3006 return PJ_SUCCESS;
3007}
Benny Prijono10454dc2009-02-21 14:21:59 +00003008
Benny Prijonof798e502009-03-09 13:08:16 +00003009/* Create audio device parameter to open the device */
3010static pj_status_t create_aud_param(pjmedia_aud_param *param,
3011 pjmedia_aud_dev_index capture_dev,
3012 pjmedia_aud_dev_index playback_dev,
3013 unsigned clock_rate,
3014 unsigned channel_count,
3015 unsigned samples_per_frame,
3016 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003017{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003018 pj_status_t status;
3019
Benny Prijono96e74f32009-02-22 12:00:12 +00003020 /* Normalize device ID with new convention about default device ID */
3021 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
3022 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
3023
Benny Prijono10454dc2009-02-21 14:21:59 +00003024 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00003025 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00003026 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00003027 pjsua_perror(THIS_FILE, "Error retrieving default audio "
3028 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00003029 return status;
3030 }
Benny Prijonof798e502009-03-09 13:08:16 +00003031 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
3032 param->rec_id = capture_dev;
3033 param->play_id = playback_dev;
3034 param->clock_rate = clock_rate;
3035 param->channel_count = channel_count;
3036 param->samples_per_frame = samples_per_frame;
3037 param->bits_per_sample = bits_per_sample;
3038
3039 /* Update the setting with user preference */
3040#define update_param(cap, field) \
3041 if (pjsua_var.aud_param.flags & cap) { \
3042 param->flags |= cap; \
3043 param->field = pjsua_var.aud_param.field; \
3044 }
3045 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3046 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3047 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3048 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3049#undef update_param
3050
Benny Prijono10454dc2009-02-21 14:21:59 +00003051 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00003052 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
3053 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
3054 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
3055 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
3056
Benny Prijono10454dc2009-02-21 14:21:59 +00003057 /* EC settings */
3058 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00003059 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
3060 param->ec_enabled = PJ_TRUE;
3061 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00003062 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00003063 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00003064 }
3065
Benny Prijonof798e502009-03-09 13:08:16 +00003066 return PJ_SUCCESS;
3067}
Benny Prijono26056d82006-10-11 16:03:41 +00003068
Benny Prijonof798e502009-03-09 13:08:16 +00003069/* Internal: the first time the audio device is opened (during app
3070 * startup), retrieve the audio settings such as volume level
3071 * so that aud_get_settings() will work.
3072 */
3073static pj_status_t update_initial_aud_param()
3074{
3075 pjmedia_aud_stream *strm;
3076 pjmedia_aud_param param;
3077 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00003078
Benny Prijonof798e502009-03-09 13:08:16 +00003079 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00003080
Benny Prijonof798e502009-03-09 13:08:16 +00003081 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00003082
Benny Prijonof798e502009-03-09 13:08:16 +00003083 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003084 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00003085 pjsua_perror(THIS_FILE, "Error audio stream "
3086 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003087 return status;
3088 }
3089
Benny Prijonof798e502009-03-09 13:08:16 +00003090#define update_saved_param(cap, field) \
3091 if (param.flags & cap) { \
3092 pjsua_var.aud_param.flags |= cap; \
3093 pjsua_var.aud_param.field = param.field; \
3094 }
3095
3096 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3097 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3098 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3099 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3100#undef update_saved_param
3101
3102 return PJ_SUCCESS;
3103}
3104
3105/* Get format name */
3106static const char *get_fmt_name(pj_uint32_t id)
3107{
3108 static char name[8];
3109
3110 if (id == PJMEDIA_FORMAT_L16)
3111 return "PCM";
3112 pj_memcpy(name, &id, 4);
3113 name[4] = '\0';
3114 return name;
3115}
3116
3117/* Open sound device with the setting. */
Sauw Ming98766c72011-03-11 06:57:24 +00003118static pj_status_t open_snd_dev(pjmedia_snd_port_param *param)
Benny Prijonof798e502009-03-09 13:08:16 +00003119{
3120 pjmedia_port *conf_port;
3121 pj_status_t status;
3122
3123 PJ_ASSERT_RETURN(param, PJ_EINVAL);
3124
3125 /* Check if NULL sound device is used */
Sauw Ming98766c72011-03-11 06:57:24 +00003126 if (NULL_SND_DEV_ID==param->base.rec_id ||
3127 NULL_SND_DEV_ID==param->base.play_id)
3128 {
Benny Prijonof798e502009-03-09 13:08:16 +00003129 return pjsua_set_null_snd_dev();
3130 }
3131
3132 /* Close existing sound port */
3133 close_snd_dev();
3134
Benny Prijono2d647722011-07-13 03:05:22 +00003135 /* Notify app */
3136 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3137 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
3138 }
3139
Benny Prijonof798e502009-03-09 13:08:16 +00003140 /* Create memory pool for sound device. */
3141 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3142 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3143
3144
3145 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
Sauw Ming98766c72011-03-11 06:57:24 +00003146 get_fmt_name(param->base.ext_fmt.id),
3147 param->base.clock_rate, param->base.channel_count,
3148 param->base.samples_per_frame / param->base.channel_count *
3149 1000 / param->base.clock_rate));
Benny Prijonof798e502009-03-09 13:08:16 +00003150
3151 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
Sauw Ming98766c72011-03-11 06:57:24 +00003152 param, &pjsua_var.snd_port);
Benny Prijonof798e502009-03-09 13:08:16 +00003153 if (status != PJ_SUCCESS)
3154 return status;
3155
3156 /* Get the port0 of the conference bridge. */
3157 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3158 pj_assert(conf_port != NULL);
3159
3160 /* For conference bridge, resample if necessary if the bridge's
3161 * clock rate is different than the sound device's clock rate.
3162 */
3163 if (!pjsua_var.is_mswitch &&
Sauw Ming98766c72011-03-11 06:57:24 +00003164 param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM &&
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003165 PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate)
Benny Prijonof798e502009-03-09 13:08:16 +00003166 {
3167 pjmedia_port *resample_port;
3168 unsigned resample_opt = 0;
3169
3170 if (pjsua_var.media_cfg.quality >= 3 &&
3171 pjsua_var.media_cfg.quality <= 4)
3172 {
3173 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
3174 }
3175 else if (pjsua_var.media_cfg.quality < 3) {
3176 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
3177 }
3178
3179 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
3180 conf_port,
Sauw Ming98766c72011-03-11 06:57:24 +00003181 param->base.clock_rate,
Benny Prijonof798e502009-03-09 13:08:16 +00003182 resample_opt,
3183 &resample_port);
3184 if (status != PJ_SUCCESS) {
3185 char errmsg[PJ_ERR_MSG_SIZE];
3186 pj_strerror(status, errmsg, sizeof(errmsg));
3187 PJ_LOG(4, (THIS_FILE,
3188 "Error creating resample port: %s",
3189 errmsg));
3190 close_snd_dev();
3191 return status;
3192 }
3193
3194 conf_port = resample_port;
3195 }
3196
3197 /* Otherwise for audio switchboard, the switch's port0 setting is
3198 * derived from the sound device setting, so update the setting.
3199 */
3200 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003201 pj_memcpy(&conf_port->info.fmt, &param->base.ext_fmt,
Benny Prijonoc45d9512010-12-10 11:04:30 +00003202 sizeof(conf_port->info.fmt));
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003203 conf_port->info.fmt.det.aud.clock_rate = param->base.clock_rate;
3204 conf_port->info.fmt.det.aud.frame_time_usec = param->base.samples_per_frame*
Benny Prijonoc45d9512010-12-10 11:04:30 +00003205 1000000 /
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003206 param->base.clock_rate;
3207 conf_port->info.fmt.det.aud.channel_count = param->base.channel_count;
Benny Prijonoc45d9512010-12-10 11:04:30 +00003208 conf_port->info.fmt.det.aud.bits_per_sample = 16;
Benny Prijonof798e502009-03-09 13:08:16 +00003209 }
3210
Benny Prijonoc45d9512010-12-10 11:04:30 +00003211
Benny Prijonof798e502009-03-09 13:08:16 +00003212 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00003213 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
3214 conf_port );
3215 if (status != PJ_SUCCESS) {
3216 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
3217 "sound device", status);
3218 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3219 pjsua_var.snd_port = NULL;
3220 return status;
3221 }
3222
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003223 /* Save the device IDs */
Sauw Ming98766c72011-03-11 06:57:24 +00003224 pjsua_var.cap_dev = param->base.rec_id;
3225 pjsua_var.play_dev = param->base.play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003226
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003227 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00003228 {
3229 pjmedia_aud_dev_info rec_info;
3230 pjmedia_aud_stream *strm;
3231 pjmedia_aud_param si;
3232 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003233
Benny Prijonof798e502009-03-09 13:08:16 +00003234 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3235 status = pjmedia_aud_stream_get_param(strm, &si);
3236 if (status == PJ_SUCCESS)
3237 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00003238
Benny Prijonof798e502009-03-09 13:08:16 +00003239 if (status==PJ_SUCCESS) {
Sauw Ming98766c72011-03-11 06:57:24 +00003240 if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) {
Benny Prijonof798e502009-03-09 13:08:16 +00003241 char tmp_buf[128];
3242 int tmp_buf_len = sizeof(tmp_buf);
3243
3244 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
3245 "%s (%dKHz)",
3246 rec_info.name,
Sauw Ming98766c72011-03-11 06:57:24 +00003247 param->base.clock_rate/1000);
Benny Prijonof798e502009-03-09 13:08:16 +00003248 pj_strset(&tmp, tmp_buf, tmp_buf_len);
3249 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
3250 } else {
3251 pjmedia_conf_set_port0_name(pjsua_var.mconf,
3252 pj_cstr(&tmp, rec_info.name));
3253 }
3254 }
3255
3256 /* Any error is not major, let it through */
3257 status = PJ_SUCCESS;
3258 };
3259
3260 /* If this is the first time the audio device is open, retrieve some
3261 * settings from the device (such as volume settings) so that the
3262 * pjsua_snd_get_setting() work.
3263 */
3264 if (pjsua_var.aud_open_cnt == 0) {
3265 update_initial_aud_param();
3266 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00003267 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003268
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003269 return PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00003270}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003271
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003272
Benny Prijonof798e502009-03-09 13:08:16 +00003273/* Close existing sound device */
3274static void close_snd_dev(void)
3275{
Benny Prijono2d647722011-07-13 03:05:22 +00003276 /* Notify app */
3277 if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3278 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0);
3279 }
3280
Benny Prijonof798e502009-03-09 13:08:16 +00003281 /* Close sound device */
3282 if (pjsua_var.snd_port) {
3283 pjmedia_aud_dev_info cap_info, play_info;
3284 pjmedia_aud_stream *strm;
3285 pjmedia_aud_param param;
3286
3287 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3288 pjmedia_aud_stream_get_param(strm, &param);
3289
3290 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
3291 cap_info.name[0] = '\0';
3292 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
3293 play_info.name[0] = '\0';
3294
3295 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
3296 "%s sound capture device",
3297 play_info.name, cap_info.name));
3298
3299 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
3300 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3301 pjsua_var.snd_port = NULL;
3302 }
3303
3304 /* Close null sound device */
3305 if (pjsua_var.null_snd) {
3306 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
3307 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
3308 pjsua_var.null_snd = NULL;
3309 }
3310
3311 if (pjsua_var.snd_pool)
3312 pj_pool_release(pjsua_var.snd_pool);
3313 pjsua_var.snd_pool = NULL;
Benny Prijono2d647722011-07-13 03:05:22 +00003314 pjsua_var.snd_is_on = PJ_FALSE;
Benny Prijonof798e502009-03-09 13:08:16 +00003315}
3316
3317
3318/*
3319 * Select or change sound device. Application may call this function at
3320 * any time to replace current sound device.
3321 */
3322PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
3323 int playback_dev)
3324{
3325 unsigned alt_cr_cnt = 1;
3326 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
3327 unsigned i;
3328 pj_status_t status = -1;
3329
Benny Prijono23ea21a2009-06-03 12:43:06 +00003330 /* Null-sound */
3331 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
3332 return pjsua_set_null_snd_dev();
3333 }
3334
Benny Prijonof798e502009-03-09 13:08:16 +00003335 /* Set default clock rate */
3336 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
3337 if (alt_cr[0] == 0)
3338 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
3339
3340 /* Allow retrying of different clock rate if we're using conference
3341 * bridge (meaning audio format is always PCM), otherwise lock on
3342 * to one clock rate.
3343 */
3344 if (pjsua_var.is_mswitch) {
3345 alt_cr_cnt = 1;
3346 } else {
3347 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
3348 }
3349
3350 /* Attempts to open the sound device with different clock rates */
3351 for (i=0; i<alt_cr_cnt; ++i) {
Sauw Ming98766c72011-03-11 06:57:24 +00003352 pjmedia_snd_port_param param;
Benny Prijonof798e502009-03-09 13:08:16 +00003353 unsigned samples_per_frame;
3354
3355 /* Create the default audio param */
3356 samples_per_frame = alt_cr[i] *
3357 pjsua_var.media_cfg.audio_frame_ptime *
3358 pjsua_var.media_cfg.channel_count / 1000;
Sauw Ming98766c72011-03-11 06:57:24 +00003359 status = create_aud_param(&param.base, capture_dev, playback_dev,
Benny Prijonof798e502009-03-09 13:08:16 +00003360 alt_cr[i], pjsua_var.media_cfg.channel_count,
3361 samples_per_frame, 16);
3362 if (status != PJ_SUCCESS)
3363 return status;
3364
3365 /* Open! */
Sauw Ming98766c72011-03-11 06:57:24 +00003366 param.options = 0;
Benny Prijonof798e502009-03-09 13:08:16 +00003367 status = open_snd_dev(&param);
3368 if (status == PJ_SUCCESS)
3369 break;
3370 }
3371
3372 if (status != PJ_SUCCESS) {
3373 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
3374 return status;
3375 }
3376
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003377 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00003378 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003379
Benny Prijonof798e502009-03-09 13:08:16 +00003380 return PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003381}
3382
3383
3384/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00003385 * Get currently active sound devices. If sound devices has not been created
3386 * (for example when pjsua_start() is not called), it is possible that
3387 * the function returns PJ_SUCCESS with -1 as device IDs.
3388 */
3389PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
3390 int *playback_dev)
3391{
3392 if (capture_dev) {
3393 *capture_dev = pjsua_var.cap_dev;
3394 }
3395 if (playback_dev) {
3396 *playback_dev = pjsua_var.play_dev;
3397 }
3398
3399 return PJ_SUCCESS;
3400}
3401
3402
3403/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003404 * Use null sound device.
3405 */
3406PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
3407{
3408 pjmedia_port *conf_port;
3409 pj_status_t status;
3410
3411 /* Close existing sound device */
3412 close_snd_dev();
3413
Benny Prijono2d647722011-07-13 03:05:22 +00003414 /* Notify app */
3415 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3416 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
3417 }
3418
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003419 /* Create memory pool for sound device. */
3420 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3421 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3422
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003423 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
3424
3425 /* Get the port0 of the conference bridge. */
3426 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3427 pj_assert(conf_port != NULL);
3428
3429 /* Create master port, connecting port0 of the conference bridge to
3430 * a null port.
3431 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003432 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003433 conf_port, 0, &pjsua_var.null_snd);
3434 if (status != PJ_SUCCESS) {
3435 pjsua_perror(THIS_FILE, "Unable to create null sound device",
3436 status);
3437 return status;
3438 }
3439
3440 /* Start the master port */
3441 status = pjmedia_master_port_start(pjsua_var.null_snd);
3442 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
3443
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003444 pjsua_var.cap_dev = NULL_SND_DEV_ID;
3445 pjsua_var.play_dev = NULL_SND_DEV_ID;
3446
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003447 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00003448 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003449
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003450 return PJ_SUCCESS;
3451}
3452
3453
Benny Prijonoe909eac2006-07-27 22:04:56 +00003454
3455/*
3456 * Use no device!
3457 */
3458PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
3459{
3460 /* Close existing sound device */
3461 close_snd_dev();
3462
3463 pjsua_var.no_snd = PJ_TRUE;
3464 return pjmedia_conf_get_master_port(pjsua_var.mconf);
3465}
3466
3467
Benny Prijonof20687a2006-08-04 18:27:19 +00003468/*
3469 * Configure the AEC settings of the sound port.
3470 */
Benny Prijono5da50432006-08-07 10:24:52 +00003471PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00003472{
3473 pjsua_var.media_cfg.ec_tail_len = tail_ms;
3474
3475 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00003476 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
3477 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00003478
3479 return PJ_SUCCESS;
3480}
3481
3482
3483/*
3484 * Get current AEC tail length.
3485 */
Benny Prijono22dfe592006-08-06 12:07:13 +00003486PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00003487{
3488 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
3489 return PJ_SUCCESS;
3490}
3491
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00003492
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003493/*
Benny Prijonof798e502009-03-09 13:08:16 +00003494 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003495 */
Benny Prijonof798e502009-03-09 13:08:16 +00003496PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003497{
Benny Prijonof798e502009-03-09 13:08:16 +00003498 return pjsua_var.snd_port != NULL;
3499}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003500
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003501
Benny Prijonof798e502009-03-09 13:08:16 +00003502/*
3503 * Configure sound device setting to the sound device being used.
3504 */
3505PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
3506 const void *pval,
3507 pj_bool_t keep)
3508{
Benny Prijono09b0ff62009-03-10 12:07:51 +00003509 pj_status_t status;
3510
Benny Prijonof798e502009-03-09 13:08:16 +00003511 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003512 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00003513 return PJMEDIA_EAUD_INVCAP;
3514 }
3515
Benny Prijono09b0ff62009-03-10 12:07:51 +00003516 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00003517 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00003518 pjmedia_aud_stream *strm;
3519
3520 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003521 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00003522 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00003523 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00003524 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00003525
3526 if (status != PJ_SUCCESS)
3527 return status;
3528
3529 /* Save in internal param for later device open */
3530 if (keep) {
3531 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
3532 cap, pval);
3533 }
3534
3535 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00003536}
3537
3538/*
3539 * Retrieve a sound device setting.
3540 */
3541PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
3542 void *pval)
3543{
3544 /* If sound device has never been opened before, open it to
3545 * retrieve the initial setting from the device (e.g. audio
3546 * volume)
3547 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003548 if (pjsua_var.aud_open_cnt==0) {
3549 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00003550 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003551 close_snd_dev();
3552 }
Benny Prijonof798e502009-03-09 13:08:16 +00003553
3554 if (pjsua_snd_is_active()) {
3555 /* Sound is active, retrieve from device directly */
3556 pjmedia_aud_stream *strm;
3557
3558 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3559 return pjmedia_aud_stream_get_cap(strm, cap, pval);
3560 } else {
3561 /* Otherwise retrieve from internal param */
3562 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
3563 cap, pval);
3564 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003565}
3566
3567
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003568/*****************************************************************************
3569 * Codecs.
3570 */
3571
3572/*
3573 * Enum all supported codecs in the system.
3574 */
3575PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
3576 unsigned *p_count )
3577{
3578 pjmedia_codec_mgr *codec_mgr;
3579 pjmedia_codec_info info[32];
3580 unsigned i, count, prio[32];
3581 pj_status_t status;
3582
3583 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3584 count = PJ_ARRAY_SIZE(info);
3585 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
3586 if (status != PJ_SUCCESS) {
3587 *p_count = 0;
3588 return status;
3589 }
3590
3591 if (count > *p_count) count = *p_count;
3592
3593 for (i=0; i<count; ++i) {
Nanang Izzuddin56b2ce42011-04-06 13:55:01 +00003594 pj_bzero(&id[i], sizeof(pjsua_codec_info));
3595
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003596 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
3597 id[i].codec_id = pj_str(id[i].buf_);
3598 id[i].priority = (pj_uint8_t) prio[i];
3599 }
3600
3601 *p_count = count;
3602
3603 return PJ_SUCCESS;
3604}
3605
3606
3607/*
3608 * Change codec priority.
3609 */
3610PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
3611 pj_uint8_t priority )
3612{
Benny Prijono88accae2008-06-26 15:48:14 +00003613 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003614 pjmedia_codec_mgr *codec_mgr;
3615
3616 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3617
Benny Prijono88accae2008-06-26 15:48:14 +00003618 if (codec_id->slen==1 && *codec_id->ptr=='*')
3619 codec_id = &all;
3620
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003621 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
3622 priority);
3623}
3624
3625
3626/*
3627 * Get codec parameters.
3628 */
3629PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
3630 pjmedia_codec_param *param )
3631{
Benny Prijono88accae2008-06-26 15:48:14 +00003632 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003633 const pjmedia_codec_info *info;
3634 pjmedia_codec_mgr *codec_mgr;
3635 unsigned count = 1;
3636 pj_status_t status;
3637
3638 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3639
Benny Prijono88accae2008-06-26 15:48:14 +00003640 if (codec_id->slen==1 && *codec_id->ptr=='*')
3641 codec_id = &all;
3642
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003643 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
3644 &count, &info, NULL);
3645 if (status != PJ_SUCCESS)
3646 return status;
3647
3648 if (count != 1)
Nanang Izzuddin50fae732011-03-22 09:49:23 +00003649 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003650
3651 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
3652 return status;
3653}
3654
3655
3656/*
3657 * Set codec parameters.
3658 */
Nanang Izzuddin06839e72010-01-27 11:48:31 +00003659PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003660 const pjmedia_codec_param *param)
3661{
Nanang Izzuddin06839e72010-01-27 11:48:31 +00003662 const pjmedia_codec_info *info[2];
3663 pjmedia_codec_mgr *codec_mgr;
3664 unsigned count = 2;
3665 pj_status_t status;
3666
3667 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3668
3669 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
3670 &count, info, NULL);
3671 if (status != PJ_SUCCESS)
3672 return status;
3673
3674 /* Codec ID should be specific, except for G.722.1 */
3675 if (count > 1 &&
3676 pj_strnicmp2(codec_id, "G7221/16", 8) != 0 &&
3677 pj_strnicmp2(codec_id, "G7221/32", 8) != 0)
3678 {
3679 pj_assert(!"Codec ID is not specific");
3680 return PJ_ETOOMANY;
3681 }
3682
3683 status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param);
3684 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003685}
Nanang Izzuddin50fae732011-03-22 09:49:23 +00003686
3687