blob: 8ede0f8cce5afa2d325eb3f3f437cabc261e8a03 [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
Benny Prijono844653c2008-12-23 17:27:53 +00003 * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
Benny Prijono32177c02008-06-20 22:44:47 +00004 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
Benny Prijonoeebe9af2006-06-13 22:57:13 +00005 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20#include <pjsua-lib/pjsua.h>
21#include <pjsua-lib/pjsua_internal.h>
22
23
24#define THIS_FILE "pjsua_media.c"
25
Nanang Izzuddin68559c32008-06-13 17:01:46 +000026#define DEFAULT_RTP_PORT 4000
27
28#define NULL_SND_DEV_ID -99
Benny Prijono80eee892007-11-03 22:43:23 +000029
30#ifndef PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
31# define PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT 0
32#endif
33
Benny Prijonoeebe9af2006-06-13 22:57:13 +000034
Benny Prijonode479562007-03-15 10:23:55 +000035/* Next RTP port to be used */
36static pj_uint16_t next_rtp_port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000037
Benny Prijonof798e502009-03-09 13:08:16 +000038/* Open sound dev */
39static pj_status_t open_snd_dev(pjmedia_aud_param *param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000040/* Close existing sound device */
41static void close_snd_dev(void);
Benny Prijonof798e502009-03-09 13:08:16 +000042/* Create audio device param */
43static pj_status_t create_aud_param(pjmedia_aud_param *param,
44 pjmedia_aud_dev_index capture_dev,
45 pjmedia_aud_dev_index playback_dev,
46 unsigned clock_rate,
47 unsigned channel_count,
48 unsigned samples_per_frame,
49 unsigned bits_per_sample);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000050
51
Benny Prijonof76e1392008-06-06 14:51:48 +000052static void pjsua_media_config_dup(pj_pool_t *pool,
53 pjsua_media_config *dst,
54 const pjsua_media_config *src)
55{
56 pj_memcpy(dst, src, sizeof(*src));
57 pj_strdup(pool, &dst->turn_server, &src->turn_server);
58 pj_stun_auth_cred_dup(pool, &dst->turn_auth_cred, &src->turn_auth_cred);
59}
60
Benny Prijonoeebe9af2006-06-13 22:57:13 +000061/**
62 * Init media subsystems.
63 */
64pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
65{
Benny Prijonoba5926a2007-05-02 11:29:37 +000066 pj_str_t codec_id = {NULL, 0};
Benny Prijono0498d902006-06-19 14:49:14 +000067 unsigned opt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000068 pj_status_t status;
69
Benny Prijonofc24e692007-01-27 18:31:51 +000070 /* To suppress warning about unused var when all codecs are disabled */
71 PJ_UNUSED_ARG(codec_id);
72
Benny Prijonof798e502009-03-09 13:08:16 +000073 /* Specify which audio device settings are save-able */
74 pjsua_var.aud_svmask = 0xFFFFFFFF;
75 /* These are not-settable */
76 pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
77 PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER |
78 PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER);
Benny Prijonoe506c8c2009-03-10 13:28:43 +000079 /* EC settings use different API */
80 pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EC |
81 PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijonof798e502009-03-09 13:08:16 +000082
Benny Prijonoeebe9af2006-06-13 22:57:13 +000083 /* Copy configuration */
Benny Prijonof76e1392008-06-06 14:51:48 +000084 pjsua_media_config_dup(pjsua_var.pool, &pjsua_var.media_cfg, cfg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000085
86 /* Normalize configuration */
Benny Prijono50f19b32008-03-11 13:15:43 +000087 if (pjsua_var.media_cfg.snd_clock_rate == 0) {
88 pjsua_var.media_cfg.snd_clock_rate = pjsua_var.media_cfg.clock_rate;
89 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +000090
91 if (pjsua_var.media_cfg.has_ioqueue &&
92 pjsua_var.media_cfg.thread_cnt == 0)
93 {
94 pjsua_var.media_cfg.thread_cnt = 1;
95 }
96
97 if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
98 pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
99 }
100
101 /* Create media endpoint. */
102 status = pjmedia_endpt_create(&pjsua_var.cp.factory,
103 pjsua_var.media_cfg.has_ioqueue? NULL :
104 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
105 pjsua_var.media_cfg.thread_cnt,
106 &pjsua_var.med_endpt);
107 if (status != PJ_SUCCESS) {
108 pjsua_perror(THIS_FILE,
109 "Media stack initialization has returned error",
110 status);
111 return status;
112 }
113
114 /* Register all codecs */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000115
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000116#if PJMEDIA_HAS_SPEEX_CODEC
117 /* Register speex. */
Nanang Izzuddin9dbad152008-06-10 18:56:10 +0000118 status = pjmedia_codec_speex_init(pjsua_var.med_endpt,
119 0,
120 pjsua_var.media_cfg.quality,
121 -1);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000122 if (status != PJ_SUCCESS) {
123 pjsua_perror(THIS_FILE, "Error initializing Speex codec",
124 status);
125 return status;
126 }
Benny Prijono7ca96da2006-08-07 12:11:40 +0000127
128 /* Set speex/16000 to higher priority*/
129 codec_id = pj_str("speex/16000");
130 pjmedia_codec_mgr_set_codec_priority(
131 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
132 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
133
134 /* Set speex/8000 to next higher priority*/
135 codec_id = pj_str("speex/8000");
136 pjmedia_codec_mgr_set_codec_priority(
137 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
138 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
139
140
141
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000142#endif /* PJMEDIA_HAS_SPEEX_CODEC */
143
Benny Prijono00cae612006-07-31 15:19:36 +0000144#if PJMEDIA_HAS_ILBC_CODEC
145 /* Register iLBC. */
146 status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt,
147 pjsua_var.media_cfg.ilbc_mode);
148 if (status != PJ_SUCCESS) {
149 pjsua_perror(THIS_FILE, "Error initializing iLBC codec",
150 status);
151 return status;
152 }
153#endif /* PJMEDIA_HAS_ILBC_CODEC */
154
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000155#if PJMEDIA_HAS_GSM_CODEC
156 /* Register GSM */
157 status = pjmedia_codec_gsm_init(pjsua_var.med_endpt);
158 if (status != PJ_SUCCESS) {
159 pjsua_perror(THIS_FILE, "Error initializing GSM codec",
160 status);
161 return status;
162 }
163#endif /* PJMEDIA_HAS_GSM_CODEC */
164
165#if PJMEDIA_HAS_G711_CODEC
166 /* Register PCMA and PCMU */
167 status = pjmedia_codec_g711_init(pjsua_var.med_endpt);
168 if (status != PJ_SUCCESS) {
169 pjsua_perror(THIS_FILE, "Error initializing G711 codec",
170 status);
171 return status;
172 }
173#endif /* PJMEDIA_HAS_G711_CODEC */
174
Benny Prijono7ffd7752008-03-17 14:07:53 +0000175#if PJMEDIA_HAS_G722_CODEC
176 status = pjmedia_codec_g722_init( pjsua_var.med_endpt );
177 if (status != PJ_SUCCESS) {
178 pjsua_perror(THIS_FILE, "Error initializing G722 codec",
179 status);
180 return status;
181 }
182#endif /* PJMEDIA_HAS_G722_CODEC */
183
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000184#if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000185 /* Register IPP codecs */
186 status = pjmedia_codec_ipp_init(pjsua_var.med_endpt);
187 if (status != PJ_SUCCESS) {
188 pjsua_perror(THIS_FILE, "Error initializing IPP codecs",
189 status);
190 return status;
191 }
192
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000193#endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000194
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000195#if PJMEDIA_HAS_PASSTHROUGH_CODECS
196 /* Register passthrough codecs */
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000197 {
198 unsigned aud_idx;
199 unsigned ext_fmt_cnt = 0;
200 pjmedia_format ext_fmts[32];
201 pjmedia_codec_passthrough_setting setting;
202
203 /* List extended formats supported by audio devices */
204 for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) {
205 pjmedia_aud_dev_info aud_info;
206 unsigned i;
207
208 status = pjmedia_aud_dev_get_info(aud_idx, &aud_info);
209 if (status != PJ_SUCCESS) {
210 pjsua_perror(THIS_FILE, "Error querying audio device info",
211 status);
212 return status;
213 }
214
215 /* Collect extended formats supported by this audio device */
216 for (i = 0; i < aud_info.ext_fmt_cnt; ++i) {
217 unsigned j;
218 pj_bool_t is_listed = PJ_FALSE;
219
220 /* See if this extended format is already in the list */
221 for (j = 0; j < ext_fmt_cnt && !is_listed; ++j) {
222 if (ext_fmts[j].id == aud_info.ext_fmt[i].id &&
223 ext_fmts[j].bitrate == aud_info.ext_fmt[i].bitrate)
224 {
225 is_listed = PJ_TRUE;
226 }
227 }
228
229 /* Put this format into the list, if it is not in the list */
230 if (!is_listed)
231 ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i];
232
233 pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts));
234 }
235 }
236
237 /* Init the passthrough codec with supported formats only */
238 setting.fmt_cnt = ext_fmt_cnt;
239 setting.fmts = ext_fmts;
Nanang Izzuddin873f3e42009-07-15 17:55:16 +0000240 setting.ilbc_mode = cfg->ilbc_mode;
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000241 status = pjmedia_codec_passthrough_init2(pjsua_var.med_endpt, &setting);
242 if (status != PJ_SUCCESS) {
243 pjsua_perror(THIS_FILE, "Error initializing passthrough codecs",
244 status);
245 return status;
246 }
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000247 }
248#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
249
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000250#if PJMEDIA_HAS_G7221_CODEC
251 /* Register G722.1 codecs */
252 status = pjmedia_codec_g7221_init(pjsua_var.med_endpt);
253 if (status != PJ_SUCCESS) {
254 pjsua_perror(THIS_FILE, "Error initializing G722.1 codec",
255 status);
256 return status;
257 }
258#endif /* PJMEDIA_HAS_G7221_CODEC */
259
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000260#if PJMEDIA_HAS_L16_CODEC
261 /* Register L16 family codecs, but disable all */
262 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
263 if (status != PJ_SUCCESS) {
264 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
265 status);
266 return status;
267 }
268
269 /* Disable ALL L16 codecs */
270 codec_id = pj_str("L16");
271 pjmedia_codec_mgr_set_codec_priority(
272 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
273 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
274
275#endif /* PJMEDIA_HAS_L16_CODEC */
276
277
278 /* Save additional conference bridge parameters for future
279 * reference.
280 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000281 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000282 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000283 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
284 pjsua_var.mconf_cfg.channel_count *
285 pjsua_var.media_cfg.audio_frame_ptime /
286 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000287
Benny Prijono0498d902006-06-19 14:49:14 +0000288 /* Init options for conference bridge. */
289 opt = PJMEDIA_CONF_NO_DEVICE;
290 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000291 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000292 {
293 opt |= PJMEDIA_CONF_SMALL_FILTER;
294 }
295 else if (pjsua_var.media_cfg.quality < 3) {
296 opt |= PJMEDIA_CONF_USE_LINEAR;
297 }
298
299
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000300 /* Init conference bridge. */
301 status = pjmedia_conf_create(pjsua_var.pool,
302 pjsua_var.media_cfg.max_media_ports,
303 pjsua_var.media_cfg.clock_rate,
304 pjsua_var.mconf_cfg.channel_count,
305 pjsua_var.mconf_cfg.samples_per_frame,
306 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000307 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000308 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000309 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000310 status);
311 return status;
312 }
313
Benny Prijonof798e502009-03-09 13:08:16 +0000314 /* Are we using the audio switchboard (a.k.a APS-Direct)? */
315 pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
316 ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
317
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000318 /* Create null port just in case user wants to use null sound. */
319 status = pjmedia_null_port_create(pjsua_var.pool,
320 pjsua_var.media_cfg.clock_rate,
321 pjsua_var.mconf_cfg.channel_count,
322 pjsua_var.mconf_cfg.samples_per_frame,
323 pjsua_var.mconf_cfg.bits_per_sample,
324 &pjsua_var.null_port);
325 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
326
Nanang Izzuddin69b69ae2009-04-14 15:18:30 +0000327#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
328 /* Initialize SRTP library. */
329 status = pjmedia_srtp_init_lib();
330 if (status != PJ_SUCCESS) {
331 pjsua_perror(THIS_FILE, "Error initializing SRTP library",
332 status);
333 return status;
334 }
335#endif
336
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000337 return PJ_SUCCESS;
338}
339
340
341/*
342 * Create RTP and RTCP socket pair, and possibly resolve their public
343 * address via STUN.
344 */
345static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
346 pjmedia_sock_info *skinfo)
347{
348 enum {
349 RTP_RETRY = 100
350 };
351 int i;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000352 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000353 pj_sockaddr_in mapped_addr[2];
354 pj_status_t status = PJ_SUCCESS;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000355 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000356 pj_sock_t sock[2];
357
Benny Prijonoc97608e2007-03-23 16:34:20 +0000358 /* Make sure STUN server resolution has completed */
Benny Prijonobb995fd2009-08-12 11:03:23 +0000359 status = resolve_stun_server(PJ_TRUE);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000360 if (status != PJ_SUCCESS) {
361 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
362 return status;
363 }
364
Benny Prijonode479562007-03-15 10:23:55 +0000365 if (next_rtp_port == 0)
366 next_rtp_port = (pj_uint16_t)cfg->port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000367
368 for (i=0; i<2; ++i)
369 sock[i] = PJ_INVALID_SOCKET;
370
Benny Prijono0a5cad82006-09-26 13:21:02 +0000371 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
372 if (cfg->bound_addr.slen) {
373 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
374 if (status != PJ_SUCCESS) {
375 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
376 status);
377 return status;
378 }
379 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000380
381 /* Loop retry to bind RTP and RTCP sockets. */
Benny Prijonode479562007-03-15 10:23:55 +0000382 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000383
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000384 /* Create RTP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000385 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000386 if (status != PJ_SUCCESS) {
387 pjsua_perror(THIS_FILE, "socket() error", status);
388 return status;
389 }
390
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000391 /* Apply QoS to RTP socket, if specified */
392 status = pj_sock_apply_qos2(sock[0], cfg->qos_type,
393 &cfg->qos_params,
394 2, THIS_FILE, "RTP socket");
395
396 /* Bind RTP socket */
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000397 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
398 next_rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000399 if (status != PJ_SUCCESS) {
400 pj_sock_close(sock[0]);
401 sock[0] = PJ_INVALID_SOCKET;
402 continue;
403 }
404
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000405 /* Create RTCP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000406 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000407 if (status != PJ_SUCCESS) {
408 pjsua_perror(THIS_FILE, "socket() error", status);
409 pj_sock_close(sock[0]);
410 return status;
411 }
412
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000413 /* Apply QoS to RTCP socket, if specified */
414 status = pj_sock_apply_qos2(sock[1], cfg->qos_type,
415 &cfg->qos_params,
416 2, THIS_FILE, "RTCP socket");
417
418 /* Bind RTCP socket */
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000419 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
420 (pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000421 if (status != PJ_SUCCESS) {
422 pj_sock_close(sock[0]);
423 sock[0] = PJ_INVALID_SOCKET;
424
425 pj_sock_close(sock[1]);
426 sock[1] = PJ_INVALID_SOCKET;
427 continue;
428 }
429
430 /*
431 * If we're configured to use STUN, then find out the mapped address,
432 * and make sure that the mapped RTCP port is adjacent with the RTP.
433 */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000434 if (pjsua_var.stun_srv.addr.sa_family != 0) {
435 char ip_addr[32];
436 pj_str_t stun_srv;
437
438 pj_ansi_strcpy(ip_addr,
439 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
440 stun_srv = pj_str(ip_addr);
441
Benny Prijono14c2b862007-02-21 00:40:05 +0000442 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000443 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
444 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000445 mapped_addr);
446 if (status != PJ_SUCCESS) {
447 pjsua_perror(THIS_FILE, "STUN resolve error", status);
448 goto on_error;
449 }
450
Benny Prijono80eee892007-11-03 22:43:23 +0000451#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000452 if (pj_ntohs(mapped_addr[1].sin_port) ==
453 pj_ntohs(mapped_addr[0].sin_port)+1)
454 {
455 /* Success! */
456 break;
457 }
458
459 pj_sock_close(sock[0]);
460 sock[0] = PJ_INVALID_SOCKET;
461
462 pj_sock_close(sock[1]);
463 sock[1] = PJ_INVALID_SOCKET;
Benny Prijono80eee892007-11-03 22:43:23 +0000464#else
465 if (pj_ntohs(mapped_addr[1].sin_port) !=
466 pj_ntohs(mapped_addr[0].sin_port)+1)
467 {
468 PJ_LOG(4,(THIS_FILE,
469 "Note: STUN mapped RTCP port %d is not adjacent"
470 " to RTP port %d",
471 pj_ntohs(mapped_addr[1].sin_port),
472 pj_ntohs(mapped_addr[0].sin_port)));
473 }
474 /* Success! */
475 break;
476#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000477
Benny Prijono0a5cad82006-09-26 13:21:02 +0000478 } else if (cfg->public_addr.slen) {
479
480 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000481 (pj_uint16_t)next_rtp_port);
Benny Prijono0a5cad82006-09-26 13:21:02 +0000482 if (status != PJ_SUCCESS)
483 goto on_error;
484
485 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000486 (pj_uint16_t)(next_rtp_port+1));
Benny Prijono0a5cad82006-09-26 13:21:02 +0000487 if (status != PJ_SUCCESS)
488 goto on_error;
489
490 break;
491
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000492 } else {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000493
Benny Prijono42d08d22007-12-20 11:23:07 +0000494 if (bound_addr.sin_addr.s_addr == 0) {
495 pj_sockaddr addr;
496
497 /* Get local IP address. */
498 status = pj_gethostip(pj_AF_INET(), &addr);
499 if (status != PJ_SUCCESS)
500 goto on_error;
501
502 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
503 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000504
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000505 for (i=0; i<2; ++i) {
506 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
Benny Prijono42d08d22007-12-20 11:23:07 +0000507 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000508 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000509
Benny Prijonode479562007-03-15 10:23:55 +0000510 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
511 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000512 break;
513 }
514 }
515
516 if (sock[0] == PJ_INVALID_SOCKET) {
517 PJ_LOG(1,(THIS_FILE,
518 "Unable to find appropriate RTP/RTCP ports combination"));
519 goto on_error;
520 }
521
522
523 skinfo->rtp_sock = sock[0];
524 pj_memcpy(&skinfo->rtp_addr_name,
525 &mapped_addr[0], sizeof(pj_sockaddr_in));
526
527 skinfo->rtcp_sock = sock[1];
528 pj_memcpy(&skinfo->rtcp_addr_name,
529 &mapped_addr[1], sizeof(pj_sockaddr_in));
530
Benny Prijono8b22ce12008-02-08 12:57:55 +0000531 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000532 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
533 sizeof(addr_buf), 3)));
Benny Prijono8b22ce12008-02-08 12:57:55 +0000534 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000535 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
536 sizeof(addr_buf), 3)));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000537
Benny Prijonode479562007-03-15 10:23:55 +0000538 next_rtp_port += 2;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000539 return PJ_SUCCESS;
540
541on_error:
542 for (i=0; i<2; ++i) {
543 if (sock[i] != PJ_INVALID_SOCKET)
544 pj_sock_close(sock[i]);
545 }
546 return status;
547}
548
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000549/* Check if sound device is idle. */
550static void check_snd_dev_idle()
551{
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000552 unsigned call_cnt;
553
554 /* Get the call count, we shouldn't close the sound device when there is
555 * any calls active.
556 */
557 call_cnt = pjsua_call_get_count();
558
559 /* When this function is called from pjsua_media_channel_deinit() upon
560 * disconnecting call, actually the call count hasn't been updated/
561 * decreased. So we put additional check here, if there is only one
562 * call and it's in DISCONNECTED state, there is actually no active
563 * call.
564 */
565 if (call_cnt == 1) {
566 pjsua_call_id call_id;
567 pj_status_t status;
568
569 status = pjsua_enum_calls(&call_id, &call_cnt);
570 if (status == PJ_SUCCESS && call_cnt > 0 &&
571 !pjsua_call_is_active(call_id))
572 {
573 call_cnt = 0;
574 }
575 }
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000576
577 /* Activate sound device auto-close timer if sound device is idle.
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000578 * It is idle when there is no port connection in the bridge and
579 * there is no active call.
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000580 */
581 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) &&
582 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
583 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000584 call_cnt == 0 &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000585 pjsua_var.media_cfg.snd_auto_close_time >= 0)
586 {
587 pj_time_val delay;
588
589 delay.msec = 0;
590 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
591
592 pjsua_var.snd_idle_timer.id = PJ_TRUE;
593 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
594 &delay);
595 }
596}
597
598
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000599/* Timer callback to close sound device */
600static void close_snd_timer_cb( pj_timer_heap_t *th,
601 pj_timer_entry *entry)
602{
603 PJ_UNUSED_ARG(th);
604
Benny Prijono0f711b42009-05-06 19:08:43 +0000605 PJSUA_LOCK();
606 if (entry->id) {
607 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
608 pjsua_var.media_cfg.snd_auto_close_time));
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000609
Benny Prijono0f711b42009-05-06 19:08:43 +0000610 entry->id = PJ_FALSE;
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000611
Benny Prijono0f711b42009-05-06 19:08:43 +0000612 close_snd_dev();
613 }
614 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000615}
616
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000617
618/*
619 * Start pjsua media subsystem.
620 */
621pj_status_t pjsua_media_subsys_start(void)
622{
623 pj_status_t status;
624
625 /* Create media for calls, if none is specified */
626 if (pjsua_var.calls[0].med_tp == NULL) {
627 pjsua_transport_config transport_cfg;
628
629 /* Create default transport config */
630 pjsua_transport_config_default(&transport_cfg);
631 transport_cfg.port = DEFAULT_RTP_PORT;
632
633 status = pjsua_media_transports_create(&transport_cfg);
634 if (status != PJ_SUCCESS)
635 return status;
636 }
637
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000638 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
639 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000640
Benny Prijonobf53b002010-01-04 13:08:31 +0000641 /* Perform NAT detection */
642 pjsua_detect_nat_type();
643
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000644 return PJ_SUCCESS;
645}
646
647
648/*
649 * Destroy pjsua media subsystem.
650 */
651pj_status_t pjsua_media_subsys_destroy(void)
652{
653 unsigned i;
654
Benny Prijono384dab42009-10-14 01:58:04 +0000655 PJ_LOG(4,(THIS_FILE, "Shutting down media.."));
656
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000657 close_snd_dev();
658
659 if (pjsua_var.mconf) {
660 pjmedia_conf_destroy(pjsua_var.mconf);
661 pjsua_var.mconf = NULL;
662 }
663
664 if (pjsua_var.null_port) {
665 pjmedia_port_destroy(pjsua_var.null_port);
666 pjsua_var.null_port = NULL;
667 }
668
669 /* Destroy file players */
670 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
671 if (pjsua_var.player[i].port) {
672 pjmedia_port_destroy(pjsua_var.player[i].port);
673 pjsua_var.player[i].port = NULL;
674 }
675 }
676
677 /* Destroy file recorders */
678 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
679 if (pjsua_var.recorder[i].port) {
680 pjmedia_port_destroy(pjsua_var.recorder[i].port);
681 pjsua_var.recorder[i].port = NULL;
682 }
683 }
684
685 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000686 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono311b63f2008-07-14 11:31:40 +0000687 if (pjsua_var.calls[i].med_tp_st != PJSUA_MED_TP_IDLE) {
688 pjsua_media_channel_deinit(i);
689 }
Benny Prijono40860c32008-09-04 13:55:33 +0000690 if (pjsua_var.calls[i].med_tp && pjsua_var.calls[i].med_tp_auto_del) {
691 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000692 }
Benny Prijono40860c32008-09-04 13:55:33 +0000693 pjsua_var.calls[i].med_tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000694 }
695
696 /* Destroy media endpoint. */
697 if (pjsua_var.med_endpt) {
698
699 /* Shutdown all codecs: */
700# if PJMEDIA_HAS_SPEEX_CODEC
701 pjmedia_codec_speex_deinit();
702# endif /* PJMEDIA_HAS_SPEEX_CODEC */
703
704# if PJMEDIA_HAS_GSM_CODEC
705 pjmedia_codec_gsm_deinit();
706# endif /* PJMEDIA_HAS_GSM_CODEC */
707
708# if PJMEDIA_HAS_G711_CODEC
709 pjmedia_codec_g711_deinit();
710# endif /* PJMEDIA_HAS_G711_CODEC */
711
Benny Prijono7ffd7752008-03-17 14:07:53 +0000712# if PJMEDIA_HAS_G722_CODEC
713 pjmedia_codec_g722_deinit();
714# endif /* PJMEDIA_HAS_G722_CODEC */
715
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000716# if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000717 pjmedia_codec_ipp_deinit();
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000718# endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000719
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000720# if PJMEDIA_HAS_PASSTHROUGH_CODECS
721 pjmedia_codec_passthrough_deinit();
722# endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
723
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000724# if PJMEDIA_HAS_G7221_CODEC
725 pjmedia_codec_g7221_deinit();
726# endif /* PJMEDIA_HAS_G7221_CODEC */
727
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000728# if PJMEDIA_HAS_L16_CODEC
729 pjmedia_codec_l16_deinit();
730# endif /* PJMEDIA_HAS_L16_CODEC */
731
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000732 pjmedia_endpt_destroy(pjsua_var.med_endpt);
733 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000734
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000735 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000736 // Not necessary, as pjmedia_snd_deinit() should have been called
737 // in pjmedia_endpt_destroy().
738 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000739 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000740
Benny Prijonode479562007-03-15 10:23:55 +0000741 /* Reset RTP port */
742 next_rtp_port = 0;
743
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000744 return PJ_SUCCESS;
745}
746
747
Benny Prijonoc97608e2007-03-23 16:34:20 +0000748/* Create normal UDP media transports */
749static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000750{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000751 unsigned i;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000752 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000753 pj_status_t status;
754
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000755 /* Create each media transport */
756 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
757
Benny Prijono617c5bc2007-04-02 19:51:21 +0000758 status = create_rtp_rtcp_sock(cfg, &skinfo);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000759 if (status != PJ_SUCCESS) {
760 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
761 status);
762 goto on_error;
763 }
Benny Prijonod8179652008-01-23 20:39:07 +0000764
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000765 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000766 &skinfo, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000767 &pjsua_var.calls[i].med_tp);
768 if (status != PJ_SUCCESS) {
769 pjsua_perror(THIS_FILE, "Unable to create media transport",
770 status);
771 goto on_error;
772 }
Benny Prijono00cae612006-07-31 15:19:36 +0000773
Benny Prijonod8179652008-01-23 20:39:07 +0000774 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
775 PJMEDIA_DIR_ENCODING,
776 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000777
Benny Prijonod8179652008-01-23 20:39:07 +0000778 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
779 PJMEDIA_DIR_DECODING,
780 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000781
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000782 }
783
Benny Prijonoc97608e2007-03-23 16:34:20 +0000784 return PJ_SUCCESS;
785
786on_error:
787 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
788 if (pjsua_var.calls[i].med_tp != NULL) {
789 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
790 pjsua_var.calls[i].med_tp = NULL;
791 }
792 }
793
794 return status;
795}
796
797
Benny Prijono096c56c2007-09-15 08:30:16 +0000798/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000799static void on_ice_complete(pjmedia_transport *tp,
800 pj_ice_strans_op op,
801 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000802{
Benny Prijonof76e1392008-06-06 14:51:48 +0000803 unsigned id;
Benny Prijono096c56c2007-09-15 08:30:16 +0000804 pj_bool_t found = PJ_FALSE;
805
Benny Prijono096c56c2007-09-15 08:30:16 +0000806 /* Find call which has this media transport */
807
808 PJSUA_LOCK();
809
Benny Prijonof76e1392008-06-06 14:51:48 +0000810 for (id=0; id<pjsua_var.ua_cfg.max_calls; ++id) {
811 if (pjsua_var.calls[id].med_tp == tp ||
812 pjsua_var.calls[id].med_orig == tp)
813 {
814 found = PJ_TRUE;
815 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000816 }
817 }
818
819 PJSUA_UNLOCK();
820
Benny Prijonof76e1392008-06-06 14:51:48 +0000821 if (!found)
822 return;
823
824 switch (op) {
825 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono224b4e22008-06-19 14:10:28 +0000826 pjsua_var.calls[id].med_tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000827 break;
828 case PJ_ICE_STRANS_OP_NEGOTIATION:
829 if (result != PJ_SUCCESS) {
830 pjsua_var.calls[id].media_st = PJSUA_CALL_MEDIA_ERROR;
831 pjsua_var.calls[id].media_dir = PJMEDIA_DIR_NONE;
832
833 if (pjsua_var.ua_cfg.cb.on_call_media_state) {
834 pjsua_var.ua_cfg.cb.on_call_media_state(id);
835 }
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000836 } else {
837 /* Send UPDATE if default transport address is different than
838 * what was advertised (ticket #881)
839 */
840 pjmedia_transport_info tpinfo;
841 pjmedia_ice_transport_info *ii = NULL;
842 unsigned i;
843
844 pjmedia_transport_info_init(&tpinfo);
845 pjmedia_transport_get_info(tp, &tpinfo);
846 for (i=0; i<tpinfo.specific_info_cnt; ++i) {
847 if (tpinfo.spc_info[i].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
848 ii = (pjmedia_ice_transport_info*)
849 tpinfo.spc_info[i].buffer;
850 break;
851 }
852 }
853
854 if (ii && ii->role==PJ_ICE_SESS_ROLE_CONTROLLING &&
855 pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name,
856 &pjsua_var.calls[id].med_rtp_addr))
857 {
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000858 pj_bool_t use_update;
859 const pj_str_t STR_UPDATE = { "UPDATE", 6 };
860 pjsip_dialog_cap_status support_update;
861 pjsip_dialog *dlg;
862
863 dlg = pjsua_var.calls[id].inv->dlg;
864 support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW,
865 NULL, &STR_UPDATE);
866 use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED);
867
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000868 PJ_LOG(4,(THIS_FILE,
869 "ICE default transport address has changed for "
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000870 "call %d, sending %s", id,
871 (use_update ? "UPDATE" : "re-INVITE")));
872
873 if (use_update)
874 pjsua_call_update(id, 0, NULL);
875 else
876 pjsua_call_reinvite(id, 0, NULL);
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000877 }
Benny Prijonof76e1392008-06-06 14:51:48 +0000878 }
879 break;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000880 case PJ_ICE_STRANS_OP_KEEP_ALIVE:
881 if (result != PJ_SUCCESS) {
882 PJ_PERROR(4,(THIS_FILE, result,
883 "ICE keep alive failure for transport %d", id));
884 }
885 if (pjsua_var.ua_cfg.cb.on_ice_transport_error) {
886 (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result,
887 NULL);
888 }
889 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000890 }
891}
892
893
Benny Prijonof76e1392008-06-06 14:51:48 +0000894/* Parse "HOST:PORT" format */
895static pj_status_t parse_host_port(const pj_str_t *host_port,
896 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000897{
Benny Prijonof76e1392008-06-06 14:51:48 +0000898 pj_str_t str_port;
899
900 str_port.ptr = pj_strchr(host_port, ':');
901 if (str_port.ptr != NULL) {
902 int iport;
903
904 host->ptr = host_port->ptr;
905 host->slen = (str_port.ptr - host->ptr);
906 str_port.ptr++;
907 str_port.slen = host_port->slen - host->slen - 1;
908 iport = (int)pj_strtoul(&str_port);
909 if (iport < 1 || iport > 65535)
910 return PJ_EINVAL;
911 *port = (pj_uint16_t)iport;
912 } else {
913 *host = *host_port;
914 *port = 0;
915 }
916
917 return PJ_SUCCESS;
918}
919
920/* Create ICE media transports (when ice is enabled) */
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000921static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
Benny Prijonof76e1392008-06-06 14:51:48 +0000922{
923 char stunip[PJ_INET6_ADDRSTRLEN];
924 pj_ice_strans_cfg ice_cfg;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000925 unsigned i;
926 pj_status_t status;
927
Benny Prijonoda9785b2007-04-02 20:43:06 +0000928 /* Make sure STUN server resolution has completed */
Benny Prijonobb995fd2009-08-12 11:03:23 +0000929 status = resolve_stun_server(PJ_TRUE);
Benny Prijonoda9785b2007-04-02 20:43:06 +0000930 if (status != PJ_SUCCESS) {
931 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
932 return status;
933 }
934
Benny Prijonof76e1392008-06-06 14:51:48 +0000935 /* Create ICE stream transport configuration */
936 pj_ice_strans_cfg_default(&ice_cfg);
937 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
938 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
939 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
940
941 ice_cfg.af = pj_AF_INET();
942 ice_cfg.resolver = pjsua_var.resolver;
943
Benny Prijono329d6382009-05-29 13:04:03 +0000944 ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
945
Benny Prijonof76e1392008-06-06 14:51:48 +0000946 /* Configure STUN settings */
947 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
948 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
949 ice_cfg.stun.server = pj_str(stunip);
950 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
951 }
Benny Prijono329d6382009-05-29 13:04:03 +0000952 if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
953 ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
Benny Prijonof76e1392008-06-06 14:51:48 +0000954
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000955 /* Copy QoS setting to STUN setting */
956 ice_cfg.stun.cfg.qos_type = cfg->qos_type;
957 pj_memcpy(&ice_cfg.stun.cfg.qos_params, &cfg->qos_params,
958 sizeof(cfg->qos_params));
959
Benny Prijonof76e1392008-06-06 14:51:48 +0000960 /* Configure TURN settings */
961 if (pjsua_var.media_cfg.enable_turn) {
962 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
963 &ice_cfg.turn.server,
964 &ice_cfg.turn.port);
965 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
966 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
967 return PJ_EINVAL;
968 }
969 if (ice_cfg.turn.port == 0)
970 ice_cfg.turn.port = 3479;
971 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
972 pj_memcpy(&ice_cfg.turn.auth_cred,
973 &pjsua_var.media_cfg.turn_auth_cred,
974 sizeof(ice_cfg.turn.auth_cred));
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000975
976 /* Copy QoS setting to TURN setting */
977 ice_cfg.turn.cfg.qos_type = cfg->qos_type;
978 pj_memcpy(&ice_cfg.turn.cfg.qos_params, &cfg->qos_params,
979 sizeof(cfg->qos_params));
Benny Prijonof76e1392008-06-06 14:51:48 +0000980 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000981
Benny Prijonoc97608e2007-03-23 16:34:20 +0000982 /* Create each media transport */
983 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono096c56c2007-09-15 08:30:16 +0000984 pjmedia_ice_cb ice_cb;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000985 char name[32];
Benny Prijonof76e1392008-06-06 14:51:48 +0000986 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000987
Benny Prijono096c56c2007-09-15 08:30:16 +0000988 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
989 ice_cb.on_ice_complete = &on_ice_complete;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000990 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i);
Benny Prijono224b4e22008-06-19 14:10:28 +0000991 pjsua_var.calls[i].med_tp_ready = PJ_EPENDING;
Benny Prijonof76e1392008-06-06 14:51:48 +0000992
993 comp_cnt = 1;
Benny Prijono551af422008-08-07 09:55:52 +0000994 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
Benny Prijonof76e1392008-06-06 14:51:48 +0000995 ++comp_cnt;
996
997 status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
998 &ice_cfg, &ice_cb,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000999 &pjsua_var.calls[i].med_tp);
1000 if (status != PJ_SUCCESS) {
1001 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
1002 status);
1003 goto on_error;
1004 }
1005
Benny Prijonof76e1392008-06-06 14:51:48 +00001006 /* Wait until transport is initialized, or time out */
1007 PJSUA_UNLOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +00001008 while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) {
Benny Prijonof76e1392008-06-06 14:51:48 +00001009 pjsua_handle_events(100);
1010 }
1011 PJSUA_LOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +00001012 if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) {
Benny Prijonof76e1392008-06-06 14:51:48 +00001013 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
Benny Prijono224b4e22008-06-19 14:10:28 +00001014 pjsua_var.calls[i].med_tp_ready);
1015 status = pjsua_var.calls[i].med_tp_ready;
Benny Prijonof76e1392008-06-06 14:51:48 +00001016 goto on_error;
1017 }
1018
Benny Prijonod8179652008-01-23 20:39:07 +00001019 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
1020 PJMEDIA_DIR_ENCODING,
1021 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono11da9bc2007-09-15 08:55:00 +00001022
Benny Prijonod8179652008-01-23 20:39:07 +00001023 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
1024 PJMEDIA_DIR_DECODING,
1025 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001026 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001027
1028 return PJ_SUCCESS;
1029
1030on_error:
1031 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
1032 if (pjsua_var.calls[i].med_tp != NULL) {
Benny Prijonoc97608e2007-03-23 16:34:20 +00001033 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001034 pjsua_var.calls[i].med_tp = NULL;
1035 }
1036 }
1037
Benny Prijonoc97608e2007-03-23 16:34:20 +00001038 return status;
1039}
1040
1041
1042/*
1043 * Create UDP media transports for all the calls. This function creates
1044 * one UDP media transport for each call.
1045 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001046PJ_DEF(pj_status_t) pjsua_media_transports_create(
1047 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001048{
1049 pjsua_transport_config cfg;
1050 unsigned i;
1051 pj_status_t status;
1052
1053
1054 /* Make sure pjsua_init() has been called */
1055 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
1056
1057 PJSUA_LOCK();
1058
1059 /* Delete existing media transports */
1060 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono40860c32008-09-04 13:55:33 +00001061 if (pjsua_var.calls[i].med_tp != NULL &&
1062 pjsua_var.calls[i].med_tp_auto_del)
1063 {
Benny Prijonoc97608e2007-03-23 16:34:20 +00001064 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
1065 pjsua_var.calls[i].med_tp = NULL;
Nanang Izzuddind704a8b2008-09-23 16:34:07 +00001066 pjsua_var.calls[i].med_orig = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001067 }
1068 }
1069
1070 /* Copy config */
1071 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
1072
Benny Prijono40860c32008-09-04 13:55:33 +00001073 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001074 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijono4d79b0f2009-10-25 09:02:07 +00001075 status = create_ice_media_transports(&cfg);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001076 } else {
1077 status = create_udp_media_transports(&cfg);
1078 }
1079
Benny Prijono40860c32008-09-04 13:55:33 +00001080 /* Set media transport auto_delete to True */
1081 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
1082 pjsua_var.calls[i].med_tp_auto_del = PJ_TRUE;
1083 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001084
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001085 PJSUA_UNLOCK();
1086
1087 return status;
1088}
1089
Benny Prijono40860c32008-09-04 13:55:33 +00001090/*
1091 * Attach application's created media transports.
1092 */
1093PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
1094 unsigned count,
1095 pj_bool_t auto_delete)
1096{
1097 unsigned i;
1098
1099 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
1100
1101 /* Assign the media transports */
1102 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
1103 if (pjsua_var.calls[i].med_tp != NULL &&
1104 pjsua_var.calls[i].med_tp_auto_del)
1105 {
1106 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
1107 }
1108
1109 pjsua_var.calls[i].med_tp = tp[i].transport;
1110 pjsua_var.calls[i].med_tp_auto_del = auto_delete;
1111 }
1112
1113 return PJ_SUCCESS;
1114}
1115
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001116
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001117static int find_audio_index(const pjmedia_sdp_session *sdp,
1118 pj_bool_t prefer_srtp)
1119{
1120 unsigned i;
1121 int audio_idx = -1;
1122
1123 for (i=0; i<sdp->media_count; ++i) {
1124 const pjmedia_sdp_media *m = sdp->media[i];
1125
1126 /* Skip if media is not audio */
1127 if (pj_stricmp2(&m->desc.media, "audio") != 0)
1128 continue;
1129
1130 /* Skip if media is disabled */
1131 if (m->desc.port == 0)
1132 continue;
1133
1134 /* Skip if transport is not supported */
1135 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") != 0 &&
1136 pj_stricmp2(&m->desc.transport, "RTP/SAVP") != 0)
1137 {
1138 continue;
1139 }
1140
1141 if (audio_idx == -1) {
1142 audio_idx = i;
1143 } else {
1144 /* We've found multiple candidates. This could happen
1145 * e.g. when remote is offering both RTP/SAVP and RTP/AVP,
1146 * or when remote for some reason offers two audio.
1147 */
1148
1149 if (prefer_srtp &&
1150 pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0)
1151 {
1152 /* Prefer RTP/SAVP when our media transport is SRTP */
1153 audio_idx = i;
1154 break;
1155 } else if (!prefer_srtp &&
1156 pj_stricmp2(&m->desc.transport, "RTP/AVP")==0)
1157 {
1158 /* Prefer RTP/AVP when our media transport is NOT SRTP */
1159 audio_idx = i;
1160 }
1161 }
1162 }
1163
1164 return audio_idx;
1165}
1166
1167
Benny Prijonoc97608e2007-03-23 16:34:20 +00001168pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
Benny Prijonod8179652008-01-23 20:39:07 +00001169 pjsip_role_e role,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001170 int security_level,
Benny Prijono224b4e22008-06-19 14:10:28 +00001171 pj_pool_t *tmp_pool,
1172 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001173 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001174{
1175 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono224b4e22008-06-19 14:10:28 +00001176 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001177
Benny Prijonod8179652008-01-23 20:39:07 +00001178#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1179 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
1180 pjmedia_srtp_setting srtp_opt;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001181 pjmedia_transport *srtp = NULL;
Benny Prijonod8179652008-01-23 20:39:07 +00001182#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +00001183
Benny Prijonod8179652008-01-23 20:39:07 +00001184 PJ_UNUSED_ARG(role);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001185
Benny Prijonod8179652008-01-23 20:39:07 +00001186 /* Return error if media transport has not been created yet
1187 * (e.g. application is starting)
1188 */
1189 if (call->med_tp == NULL) {
Benny Prijono03789052008-09-16 14:30:50 +00001190 if (sip_err_code)
1191 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
Benny Prijonod8179652008-01-23 20:39:07 +00001192 return PJ_EBUSY;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001193 }
1194
Benny Prijonod8179652008-01-23 20:39:07 +00001195#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Benny Prijono53a7c702008-04-14 02:57:29 +00001196 /* This function may be called when SRTP transport already exists
1197 * (e.g: in re-invite, update), don't need to destroy/re-create.
1198 */
1199 if (!call->med_orig || call->med_tp == call->med_orig) {
1200
1201 /* Check if SRTP requires secure signaling */
1202 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
1203 if (security_level < acc->cfg.srtp_secure_signaling) {
1204 if (sip_err_code)
1205 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1206 return PJSIP_ESESSIONINSECURE;
1207 }
Benny Prijonod8179652008-01-23 20:39:07 +00001208 }
Benny Prijonod8179652008-01-23 20:39:07 +00001209
Benny Prijono53a7c702008-04-14 02:57:29 +00001210 /* Always create SRTP adapter */
1211 pjmedia_srtp_setting_default(&srtp_opt);
1212 srtp_opt.close_member_tp = PJ_FALSE;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001213 /* If media session has been ever established, let's use remote's
1214 * preference in SRTP usage policy, especially when it is stricter.
1215 */
1216 if (call->rem_srtp_use > acc->cfg.use_srtp)
1217 srtp_opt.use = call->rem_srtp_use;
1218 else
1219 srtp_opt.use = acc->cfg.use_srtp;
1220
Benny Prijono53a7c702008-04-14 02:57:29 +00001221 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
1222 call->med_tp,
1223 &srtp_opt, &srtp);
1224 if (status != PJ_SUCCESS) {
1225 if (sip_err_code)
1226 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
1227 return status;
1228 }
Benny Prijonod8179652008-01-23 20:39:07 +00001229
Benny Prijono53a7c702008-04-14 02:57:29 +00001230 /* Set SRTP as current media transport */
1231 call->med_orig = call->med_tp;
1232 call->med_tp = srtp;
1233 }
Benny Prijonod8179652008-01-23 20:39:07 +00001234#else
1235 call->med_orig = call->med_tp;
1236 PJ_UNUSED_ARG(security_level);
1237#endif
1238
Benny Prijonoa310bd22008-06-27 21:19:44 +00001239 /* Find out which media line in SDP that we support. If we are offerer,
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001240 * audio will be initialized at index 0 in SDP.
Benny Prijonoa310bd22008-06-27 21:19:44 +00001241 */
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001242 if (rem_sdp == NULL) {
Benny Prijonoa310bd22008-06-27 21:19:44 +00001243 call->audio_idx = 0;
1244 }
1245 /* Otherwise find out the candidate audio media line in SDP */
1246 else {
Benny Prijonoa310bd22008-06-27 21:19:44 +00001247 pj_bool_t srtp_active;
1248
1249#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001250 srtp_active = acc->cfg.use_srtp;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001251#else
1252 srtp_active = PJ_FALSE;
1253#endif
1254
1255 /* Media count must have been checked */
1256 pj_assert(rem_sdp->media_count != 0);
1257
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001258 call->audio_idx = find_audio_index(rem_sdp, srtp_active);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001259 }
1260
1261 /* Reject offer if we couldn't find a good m=audio line in offer */
1262 if (call->audio_idx < 0) {
Benny Prijonoab8dba92008-06-27 21:59:15 +00001263 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001264 pjsua_media_channel_deinit(call_id);
Benny Prijonoab8dba92008-06-27 21:59:15 +00001265 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001266 }
1267
1268 PJ_LOG(4,(THIS_FILE, "Media index %d selected for call %d",
1269 call->audio_idx, call->index));
1270
Benny Prijono224b4e22008-06-19 14:10:28 +00001271 /* Create the media transport */
1272 status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001273 rem_sdp, call->audio_idx);
Benny Prijono224b4e22008-06-19 14:10:28 +00001274 if (status != PJ_SUCCESS) {
1275 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1276 pjsua_media_channel_deinit(call_id);
1277 return status;
1278 }
1279
1280 call->med_tp_st = PJSUA_MED_TP_INIT;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001281 return PJ_SUCCESS;
1282}
1283
1284pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1285 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001286 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001287 pjmedia_sdp_session **p_sdp,
1288 int *sip_status_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001289{
Benny Prijonoa310bd22008-06-27 21:19:44 +00001290 enum { MAX_MEDIA = 1 };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001291 pjmedia_sdp_session *sdp;
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001292 pjmedia_transport_info tpinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001293 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001294 pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001295 pj_status_t status;
1296
Benny Prijono55e82352007-05-10 20:49:08 +00001297 /* Return error if media transport has not been created yet
1298 * (e.g. application is starting)
1299 */
1300 if (call->med_tp == NULL) {
1301 return PJ_EBUSY;
1302 }
1303
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001304 if (rem_sdp && rem_sdp->media_count != 0) {
1305 pj_bool_t srtp_active;
1306
1307#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1308 srtp_active = pjsua_var.acc[call->acc_id].cfg.use_srtp;
1309#else
1310 srtp_active = PJ_FALSE;
1311#endif
1312
1313 call->audio_idx = find_audio_index(rem_sdp, srtp_active);
1314 }
1315
Benny Prijonoa310bd22008-06-27 21:19:44 +00001316 /* Media index must have been determined before */
1317 pj_assert(call->audio_idx != -1);
1318
Benny Prijono224b4e22008-06-19 14:10:28 +00001319 /* Create media if it's not created. This could happen when call is
1320 * currently on-hold
1321 */
1322 if (call->med_tp_st == PJSUA_MED_TP_IDLE) {
1323 pjsip_role_e role;
1324 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1325 status = pjsua_media_channel_init(call_id, role, call->secure_level,
1326 pool, rem_sdp, sip_status_code);
1327 if (status != PJ_SUCCESS)
1328 return status;
1329 }
1330
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001331 /* Get SDP negotiator state */
1332 if (call->inv && call->inv->neg)
1333 sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg);
1334
Benny Prijono617c5bc2007-04-02 19:51:21 +00001335 /* Get media socket info */
Benny Prijono734fc2d2008-03-17 16:05:35 +00001336 pjmedia_transport_info_init(&tpinfo);
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001337 pjmedia_transport_get_info(call->med_tp, &tpinfo);
Benny Prijono617c5bc2007-04-02 19:51:21 +00001338
1339 /* Create SDP */
Benny Prijonod8179652008-01-23 20:39:07 +00001340 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA,
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001341 &tpinfo.sock_info, &sdp);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001342 if (status != PJ_SUCCESS) {
1343 if (sip_status_code) *sip_status_code = 500;
Benny Prijono224b4e22008-06-19 14:10:28 +00001344 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001345 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001346
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001347 /* If we're answering or updating the session with a new offer,
1348 * and the selected media is not the first media
Benny Prijonoa310bd22008-06-27 21:19:44 +00001349 * in SDP, then fill in the unselected media with with zero port.
1350 * Otherwise we'll crash in transport_encode_sdp() because the media
1351 * lines are not aligned between offer and answer.
1352 */
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001353 if (call->audio_idx != 0 &&
1354 (rem_sdp || sdp_neg_state==PJMEDIA_SDP_NEG_STATE_DONE))
1355 {
Benny Prijonoa310bd22008-06-27 21:19:44 +00001356 unsigned i;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001357 const pjmedia_sdp_session *ref_sdp = rem_sdp;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001358
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001359 if (!ref_sdp) {
1360 /* We are updating session with a new offer */
1361 status = pjmedia_sdp_neg_get_active_local(call->inv->neg,
1362 &ref_sdp);
1363 pj_assert(status == PJ_SUCCESS);
1364 }
1365
1366 for (i=0; i<ref_sdp->media_count; ++i) {
1367 const pjmedia_sdp_media *ref_m = ref_sdp->media[i];
Benny Prijonoa310bd22008-06-27 21:19:44 +00001368 pjmedia_sdp_media *m;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001369
1370 if ((int)i == call->audio_idx)
1371 continue;
1372
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001373 m = pjmedia_sdp_media_clone_deactivate(pool, ref_m);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001374 if (i==sdp->media_count)
1375 sdp->media[sdp->media_count++] = m;
1376 else {
1377 pj_array_insert(sdp->media, sizeof(sdp->media[0]),
1378 sdp->media_count, i, &m);
1379 ++sdp->media_count;
1380 }
1381 }
1382 }
1383
Benny Prijono6ba8c542007-10-16 01:34:14 +00001384 /* Add NAT info in the SDP */
1385 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1386 pjmedia_sdp_attr *a;
1387 pj_str_t value;
1388 char nat_info[80];
1389
1390 value.ptr = nat_info;
1391 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1392 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1393 "%d", pjsua_var.nat_type);
1394 } else {
1395 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1396 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1397 "%d %s",
1398 pjsua_var.nat_type,
1399 type_name);
1400 }
1401
1402 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1403
1404 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1405
1406 }
1407
Benny Prijonod8179652008-01-23 20:39:07 +00001408 /* Give the SDP to media transport */
Benny Prijono224b4e22008-06-19 14:10:28 +00001409 status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001410 call->audio_idx);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001411 if (status != PJ_SUCCESS) {
1412 if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono224b4e22008-06-19 14:10:28 +00001413 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001414 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001415
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001416#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1417 /* Check if SRTP is in optional mode and configured to use duplicated
1418 * media, i.e: secured and unsecured version, in the SDP offer.
1419 */
1420 if (!rem_sdp &&
1421 pjsua_var.acc[call->acc_id].cfg.use_srtp == PJMEDIA_SRTP_OPTIONAL &&
1422 pjsua_var.acc[call->acc_id].cfg.srtp_optional_dup_offer)
1423 {
1424 unsigned i;
1425
1426 for (i = 0; i < sdp->media_count; ++i) {
1427 pjmedia_sdp_media *m = sdp->media[i];
1428
1429 /* Check if this media is unsecured but has SDP "crypto"
1430 * attribute.
1431 */
1432 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") == 0 &&
1433 pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL)
1434 {
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001435 if (i == (unsigned)call->audio_idx &&
1436 sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE)
1437 {
1438 /* This is a session update, and peer has chosen the
1439 * unsecured version, so let's make this unsecured too.
1440 */
1441 pjmedia_sdp_media_remove_all_attr(m, "crypto");
1442 } else {
1443 /* This is new offer, duplicate media so we'll have
1444 * secured (with "RTP/SAVP" transport) and and unsecured
1445 * versions.
1446 */
1447 pjmedia_sdp_media *new_m;
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001448
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001449 /* Duplicate this media and apply secured transport */
1450 new_m = pjmedia_sdp_media_clone(pool, m);
1451 pj_strdup2(pool, &new_m->desc.transport, "RTP/SAVP");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001452
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001453 /* Remove the "crypto" attribute in the unsecured media */
1454 pjmedia_sdp_media_remove_all_attr(m, "crypto");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001455
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001456 /* Insert the new media before the unsecured media */
1457 if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) {
1458 pj_array_insert(sdp->media, sizeof(new_m),
1459 sdp->media_count, i, &new_m);
1460 ++sdp->media_count;
1461 ++i;
1462 }
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001463 }
1464 }
1465 }
1466 }
1467#endif
1468
Benny Prijonof5d9f1f2009-10-14 13:13:18 +00001469 /* Update currently advertised RTP source address */
1470 pj_memcpy(&call->med_rtp_addr, &tpinfo.sock_info.rtp_addr_name,
1471 sizeof(pj_sockaddr));
1472
Benny Prijonoc97608e2007-03-23 16:34:20 +00001473 *p_sdp = sdp;
1474 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001475}
1476
1477
1478static void stop_media_session(pjsua_call_id call_id)
1479{
1480 pjsua_call *call = &pjsua_var.calls[call_id];
1481
1482 if (call->conf_slot != PJSUA_INVALID_ID) {
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001483 if (pjsua_var.mconf) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001484 pjsua_conf_remove_port(call->conf_slot);
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001485 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001486 call->conf_slot = PJSUA_INVALID_ID;
1487 }
1488
1489 if (call->session) {
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001490 pjmedia_rtcp_stat stat;
1491
Nanang Izzuddin8ea7eb02009-11-09 08:11:34 +00001492 if ((call->media_dir & PJMEDIA_DIR_ENCODING) &&
1493 (pjmedia_session_get_stream_stat(call->session, 0, &stat)
1494 == PJ_SUCCESS))
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001495 {
1496 /* Save RTP timestamp & sequence, so when media session is
1497 * restarted, those values will be restored as the initial
1498 * RTP timestamp & sequence of the new media session. So in
1499 * the same call session, RTP timestamp and sequence are
1500 * guaranteed to be contigue.
1501 */
1502 call->rtp_tx_seq_ts_set = 1 | (1 << 1);
1503 call->rtp_tx_seq = stat.rtp_tx_last_seq;
1504 call->rtp_tx_ts = stat.rtp_tx_last_ts;
1505 }
1506
Benny Prijonofc13bf62008-02-20 08:56:15 +00001507 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1508 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0);
1509 }
1510
Benny Prijonoc97608e2007-03-23 16:34:20 +00001511 pjmedia_session_destroy(call->session);
1512 call->session = NULL;
1513
1514 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
1515 call_id));
1516
1517 }
1518
1519 call->media_st = PJSUA_CALL_MEDIA_NONE;
1520}
1521
1522pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1523{
1524 pjsua_call *call = &pjsua_var.calls[call_id];
1525
1526 stop_media_session(call_id);
1527
Benny Prijono224b4e22008-06-19 14:10:28 +00001528 if (call->med_tp_st != PJSUA_MED_TP_IDLE) {
1529 pjmedia_transport_media_stop(call->med_tp);
1530 call->med_tp_st = PJSUA_MED_TP_IDLE;
1531 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001532
Benny Prijono311b63f2008-07-14 11:31:40 +00001533 if (call->med_orig && call->med_tp && call->med_tp != call->med_orig) {
Benny Prijonod8179652008-01-23 20:39:07 +00001534 pjmedia_transport_close(call->med_tp);
1535 call->med_tp = call->med_orig;
1536 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00001537
1538 check_snd_dev_idle();
1539
Benny Prijonoc97608e2007-03-23 16:34:20 +00001540 return PJ_SUCCESS;
1541}
1542
1543
1544/*
1545 * DTMF callback from the stream.
1546 */
1547static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1548 int digit)
1549{
1550 PJ_UNUSED_ARG(strm);
1551
Benny Prijono0c068262008-02-14 14:38:52 +00001552 /* For discussions about call mutex protection related to this
1553 * callback, please see ticket #460:
1554 * http://trac.pjsip.org/repos/ticket/460#comment:4
1555 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001556 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1557 pjsua_call_id call_id;
1558
Benny Prijonod8179652008-01-23 20:39:07 +00001559 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001560 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1561 }
1562}
1563
1564
1565pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijono224b4e22008-06-19 14:10:28 +00001566 const pjmedia_sdp_session *local_sdp,
Benny Prijonodbce2cf2007-03-28 16:24:00 +00001567 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001568{
1569 int prev_media_st = 0;
1570 pjsua_call *call = &pjsua_var.calls[call_id];
1571 pjmedia_session_info sess_info;
Benny Prijono91e567e2007-12-28 08:51:58 +00001572 pjmedia_stream_info *si = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001573 pjmedia_port *media_port;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001574 pj_status_t status;
1575
Benny Prijono4e5c3f52010-04-29 12:11:51 +00001576 if (!pjsua_var.med_endpt) {
1577 /* We're being shutdown */
1578 return PJ_EBUSY;
1579 }
1580
Benny Prijonoc97608e2007-03-23 16:34:20 +00001581 /* Destroy existing media session, if any. */
1582 prev_media_st = call->media_st;
1583 stop_media_session(call->index);
1584
1585 /* Create media session info based on SDP parameters.
Benny Prijonoc97608e2007-03-23 16:34:20 +00001586 */
Benny Prijono40d62b62009-08-12 17:53:47 +00001587 status = pjmedia_session_info_from_sdp( call->inv->pool_prov,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001588 pjsua_var.med_endpt,
Benny Prijono91e567e2007-12-28 08:51:58 +00001589 PJMEDIA_MAX_SDP_MEDIA, &sess_info,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001590 local_sdp, remote_sdp);
1591 if (status != PJ_SUCCESS)
1592 return status;
1593
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001594 /* Update audio index from the negotiated SDP */
1595 call->audio_idx = find_audio_index(local_sdp, PJ_TRUE);
1596
Benny Prijonoa310bd22008-06-27 21:19:44 +00001597 /* Find which session is audio */
1598 PJ_ASSERT_RETURN(call->audio_idx != -1, PJ_EBUG);
1599 PJ_ASSERT_RETURN(call->audio_idx < (int)sess_info.stream_cnt, PJ_EBUG);
1600 si = &sess_info.stream_info[call->audio_idx];
Benny Prijono91e567e2007-12-28 08:51:58 +00001601
1602 /* Reset session info with only one media stream */
1603 sess_info.stream_cnt = 1;
Nanang Izzuddin5e39a2b2010-09-20 06:13:02 +00001604 if (si != &sess_info.stream_info[0]) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001605 pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info));
Nanang Izzuddin5e39a2b2010-09-20 06:13:02 +00001606 si = &sess_info.stream_info[0];
1607 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001608
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001609 /* Check if no media is active */
Benny Prijono91e567e2007-12-28 08:51:58 +00001610 if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001611 {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001612 /* Call media state */
1613 call->media_st = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001614
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001615 /* Call media direction */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001616 call->media_dir = PJMEDIA_DIR_NONE;
1617
Benny Prijono2dbf5072010-06-23 12:38:28 +00001618 /* Don't stop transport because we need to transmit keep-alives, and
1619 * also to prevent restarting ICE negotiation. See
1620 * http://trac.pjsip.org/repos/ticket/1094
1621 */
1622#if 0
Benny Prijonod8179652008-01-23 20:39:07 +00001623 /* Shutdown transport's session */
1624 pjmedia_transport_media_stop(call->med_tp);
Benny Prijono224b4e22008-06-19 14:10:28 +00001625 call->med_tp_st = PJSUA_MED_TP_IDLE;
Benny Prijono667952e2007-04-02 19:27:54 +00001626
Benny Prijonoc97608e2007-03-23 16:34:20 +00001627 /* No need because we need keepalive? */
1628
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001629 /* Close upper entry of transport stack */
1630 if (call->med_orig && (call->med_tp != call->med_orig)) {
1631 pjmedia_transport_close(call->med_tp);
1632 call->med_tp = call->med_orig;
1633 }
Benny Prijono2dbf5072010-06-23 12:38:28 +00001634#endif
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001635
Benny Prijonoc97608e2007-03-23 16:34:20 +00001636 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001637 pjmedia_transport_info tp_info;
1638
Benny Prijono224b4e22008-06-19 14:10:28 +00001639 /* Start/restart media transport */
Benny Prijonod8179652008-01-23 20:39:07 +00001640 status = pjmedia_transport_media_start(call->med_tp,
Benny Prijono40d62b62009-08-12 17:53:47 +00001641 call->inv->pool_prov,
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001642 local_sdp, remote_sdp,
1643 call->audio_idx);
Benny Prijonod8179652008-01-23 20:39:07 +00001644 if (status != PJ_SUCCESS)
1645 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001646
Benny Prijono224b4e22008-06-19 14:10:28 +00001647 call->med_tp_st = PJSUA_MED_TP_RUNNING;
1648
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001649 /* Get remote SRTP usage policy */
1650 pjmedia_transport_info_init(&tp_info);
1651 pjmedia_transport_get_info(call->med_tp, &tp_info);
1652 if (tp_info.specific_info_cnt > 0) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +00001653 unsigned i;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001654 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1655 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1656 {
1657 pjmedia_srtp_info *srtp_info =
1658 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1659
1660 call->rem_srtp_use = srtp_info->peer_use;
1661 break;
1662 }
1663 }
1664 }
1665
Benny Prijonoc97608e2007-03-23 16:34:20 +00001666 /* Override ptime, if this option is specified. */
1667 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001668 si->param->setting.frm_per_pkt = (pj_uint8_t)
1669 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1670 if (si->param->setting.frm_per_pkt == 0)
1671 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001672 }
1673
1674 /* Disable VAD, if this option is specified. */
1675 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001676 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001677 }
1678
1679
1680 /* Optionally, application may modify other stream settings here
1681 * (such as jitter buffer parameters, codec ptime, etc.)
1682 */
Benny Prijono91e567e2007-12-28 08:51:58 +00001683 si->jb_init = pjsua_var.media_cfg.jb_init;
1684 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1685 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1686 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001687
Benny Prijono8147f402007-11-21 14:50:07 +00001688 /* Set SSRC */
Benny Prijono91e567e2007-12-28 08:51:58 +00001689 si->ssrc = call->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00001690
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001691 /* Set RTP timestamp & sequence, normally these value are intialized
1692 * automatically when stream session created, but for some cases (e.g:
1693 * call reinvite, call update) timestamp and sequence need to be kept
1694 * contigue.
1695 */
1696 si->rtp_ts = call->rtp_tx_ts;
1697 si->rtp_seq = call->rtp_tx_seq;
1698 si->rtp_seq_ts_set = call->rtp_tx_seq_ts_set;
1699
Nanang Izzuddin5e39a2b2010-09-20 06:13:02 +00001700#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
1701 /* Enable/disable stream keep-alive and NAT hole punch. */
1702 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
1703#endif
1704
Benny Prijonoc97608e2007-03-23 16:34:20 +00001705 /* Create session based on session info. */
1706 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
1707 &call->med_tp,
1708 call, &call->session );
1709 if (status != PJ_SUCCESS) {
1710 return status;
1711 }
1712
1713 /* If DTMF callback is installed by application, install our
1714 * callback to the session.
1715 */
1716 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1717 pjmedia_session_set_dtmf_callback(call->session, 0,
1718 &dtmf_callback,
Benny Prijonod8179652008-01-23 20:39:07 +00001719 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001720 }
1721
1722 /* Get the port interface of the first stream in the session.
1723 * We need the port interface to add to the conference bridge.
1724 */
1725 pjmedia_session_get_port(call->session, 0, &media_port);
1726
Benny Prijonofc13bf62008-02-20 08:56:15 +00001727 /* Notify application about stream creation.
1728 * Note: application may modify media_port to point to different
1729 * media port
1730 */
1731 if (pjsua_var.ua_cfg.cb.on_stream_created) {
1732 pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session,
1733 0, &media_port);
1734 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001735
1736 /*
1737 * Add the call to conference bridge.
1738 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001739 {
1740 char tmp[PJSIP_MAX_URL_SIZE];
1741 pj_str_t port_name;
1742
1743 port_name.ptr = tmp;
1744 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1745 call->inv->dlg->remote.info->uri,
1746 tmp, sizeof(tmp));
1747 if (port_name.slen < 1) {
1748 port_name = pj_str("call");
1749 }
Benny Prijono40d62b62009-08-12 17:53:47 +00001750 status = pjmedia_conf_add_port( pjsua_var.mconf,
1751 call->inv->pool_prov,
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001752 media_port,
1753 &port_name,
1754 (unsigned*)&call->conf_slot);
1755 if (status != PJ_SUCCESS) {
1756 return status;
1757 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001758 }
1759
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001760 /* Call media direction */
Benny Prijono91e567e2007-12-28 08:51:58 +00001761 call->media_dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001762
1763 /* Call media state */
1764 if (call->local_hold)
1765 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
1766 else if (call->media_dir == PJMEDIA_DIR_DECODING)
1767 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
1768 else
1769 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001770 }
1771
1772 /* Print info. */
1773 {
1774 char info[80];
1775 int info_len = 0;
1776 unsigned i;
1777
1778 for (i=0; i<sess_info.stream_cnt; ++i) {
1779 int len;
1780 const char *dir;
1781 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1782
1783 switch (strm_info->dir) {
1784 case PJMEDIA_DIR_NONE:
1785 dir = "inactive";
1786 break;
1787 case PJMEDIA_DIR_ENCODING:
1788 dir = "sendonly";
1789 break;
1790 case PJMEDIA_DIR_DECODING:
1791 dir = "recvonly";
1792 break;
1793 case PJMEDIA_DIR_ENCODING_DECODING:
1794 dir = "sendrecv";
1795 break;
1796 default:
1797 dir = "unknown";
1798 break;
1799 }
1800 len = pj_ansi_sprintf( info+info_len,
1801 ", stream #%d: %.*s (%s)", i,
1802 (int)strm_info->fmt.encoding_name.slen,
1803 strm_info->fmt.encoding_name.ptr,
1804 dir);
1805 if (len > 0)
1806 info_len += len;
1807 }
1808 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1809 }
1810
1811 return PJ_SUCCESS;
1812}
1813
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001814/*
1815 * Get maxinum number of conference ports.
1816 */
1817PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1818{
1819 return pjsua_var.media_cfg.max_media_ports;
1820}
1821
1822
1823/*
1824 * Get current number of active ports in the bridge.
1825 */
1826PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1827{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001828 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001829 unsigned count = PJ_ARRAY_SIZE(ports);
1830 pj_status_t status;
1831
1832 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1833 if (status != PJ_SUCCESS)
1834 count = 0;
1835
1836 return count;
1837}
1838
1839
1840/*
1841 * Enumerate all conference ports.
1842 */
1843PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1844 unsigned *count)
1845{
1846 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1847}
1848
1849
1850/*
1851 * Get information about the specified conference port
1852 */
1853PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1854 pjsua_conf_port_info *info)
1855{
1856 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001857 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001858 pj_status_t status;
1859
1860 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1861 if (status != PJ_SUCCESS)
1862 return status;
1863
Benny Prijonoac623b32006-07-03 15:19:31 +00001864 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001865 info->slot_id = id;
1866 info->name = cinfo.name;
1867 info->clock_rate = cinfo.clock_rate;
1868 info->channel_count = cinfo.channel_count;
1869 info->samples_per_frame = cinfo.samples_per_frame;
1870 info->bits_per_sample = cinfo.bits_per_sample;
1871
1872 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001873 info->listener_cnt = cinfo.listener_cnt;
1874 for (i=0; i<cinfo.listener_cnt; ++i) {
1875 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001876 }
1877
1878 return PJ_SUCCESS;
1879}
1880
1881
1882/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001883 * Add arbitrary media port to PJSUA's conference bridge.
1884 */
1885PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1886 pjmedia_port *port,
1887 pjsua_conf_port_id *p_id)
1888{
1889 pj_status_t status;
1890
1891 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1892 port, NULL, (unsigned*)p_id);
1893 if (status != PJ_SUCCESS) {
1894 if (p_id)
1895 *p_id = PJSUA_INVALID_ID;
1896 }
1897
1898 return status;
1899}
1900
1901
1902/*
1903 * Remove arbitrary slot from the conference bridge.
1904 */
1905PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1906{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001907 pj_status_t status;
1908
1909 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1910 check_snd_dev_idle();
1911
1912 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00001913}
1914
1915
1916/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001917 * Establish unidirectional media flow from souce to sink.
1918 */
1919PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1920 pjsua_conf_port_id sink)
1921{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001922 /* If sound device idle timer is active, cancel it first. */
Benny Prijono0f711b42009-05-06 19:08:43 +00001923 PJSUA_LOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001924 if (pjsua_var.snd_idle_timer.id) {
1925 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
1926 pjsua_var.snd_idle_timer.id = PJ_FALSE;
1927 }
Benny Prijono0f711b42009-05-06 19:08:43 +00001928 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001929
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001930
Benny Prijonof798e502009-03-09 13:08:16 +00001931 /* For audio switchboard (i.e. APS-Direct):
1932 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001933 * (format, clock rate, channel count) must match to peer's.
1934 * Note that sound device can be reopened only if it doesn't have
1935 * any connection.
1936 */
Benny Prijonof798e502009-03-09 13:08:16 +00001937 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001938 pjmedia_conf_port_info port0_info;
1939 pjmedia_conf_port_info peer_info;
1940 unsigned peer_id;
1941 pj_bool_t need_reopen = PJ_FALSE;
1942 pj_status_t status;
1943
1944 peer_id = (source!=0)? source : sink;
1945 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
1946 &peer_info);
1947 pj_assert(status == PJ_SUCCESS);
1948
1949 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
1950 pj_assert(status == PJ_SUCCESS);
1951
1952 /* Check if sound device is instantiated. */
1953 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1954 !pjsua_var.no_snd);
1955
1956 /* Check if sound device need to reopen because it needs to modify
1957 * settings to match its peer. Sound device must be idle in this case
1958 * though.
1959 */
1960 if (!need_reopen &&
1961 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
1962 {
1963 need_reopen = (peer_info.format.id != port0_info.format.id ||
1964 peer_info.format.bitrate != port0_info.format.bitrate ||
1965 peer_info.clock_rate != port0_info.clock_rate ||
1966 peer_info.channel_count != port0_info.channel_count);
1967 }
1968
1969 if (need_reopen) {
Benny Prijonod65f78c2009-06-03 18:59:37 +00001970 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
1971 pjmedia_aud_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001972
Benny Prijonod65f78c2009-06-03 18:59:37 +00001973 /* Create parameter based on peer info */
1974 status = create_aud_param(&param, pjsua_var.cap_dev,
1975 pjsua_var.play_dev,
1976 peer_info.clock_rate,
1977 peer_info.channel_count,
1978 peer_info.samples_per_frame,
1979 peer_info.bits_per_sample);
1980 if (status != PJ_SUCCESS) {
1981 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1982 return status;
1983 }
Benny Prijonof798e502009-03-09 13:08:16 +00001984
Benny Prijonod65f78c2009-06-03 18:59:37 +00001985 /* And peer format */
1986 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
1987 param.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
1988 param.ext_fmt = peer_info.format;
1989 }
Benny Prijono10454dc2009-02-21 14:21:59 +00001990
Benny Prijonod65f78c2009-06-03 18:59:37 +00001991 status = open_snd_dev(&param);
1992 if (status != PJ_SUCCESS) {
1993 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1994 return status;
1995 }
1996 } else {
1997 /* Null-audio */
1998 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1999 if (status != PJ_SUCCESS) {
2000 pjsua_perror(THIS_FILE, "Error opening sound device", status);
2001 return status;
2002 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002003 }
2004 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002005
Benny Prijonof798e502009-03-09 13:08:16 +00002006 } else {
2007 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002008
Benny Prijonof798e502009-03-09 13:08:16 +00002009 /* Create sound port if none is instantiated */
2010 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2011 !pjsua_var.no_snd)
2012 {
2013 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002014
Benny Prijonof798e502009-03-09 13:08:16 +00002015 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
2016 if (status != PJ_SUCCESS) {
2017 pjsua_perror(THIS_FILE, "Error opening sound device", status);
2018 return status;
2019 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002020 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002021
Benny Prijonof798e502009-03-09 13:08:16 +00002022 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002023
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002024 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
2025}
2026
2027
2028/*
2029 * Disconnect media flow from the source to destination port.
2030 */
2031PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
2032 pjsua_conf_port_id sink)
2033{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002034 pj_status_t status;
2035
2036 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002037 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002038
2039 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002040}
2041
2042
Benny Prijono6dd967c2006-12-26 02:27:14 +00002043/*
2044 * Adjust the signal level to be transmitted from the bridge to the
2045 * specified port by making it louder or quieter.
2046 */
2047PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
2048 float level)
2049{
2050 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
2051 (int)((level-1) * 128));
2052}
2053
2054/*
2055 * Adjust the signal level to be received from the specified port (to
2056 * the bridge) by making it louder or quieter.
2057 */
2058PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
2059 float level)
2060{
2061 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
2062 (int)((level-1) * 128));
2063}
2064
2065
2066/*
2067 * Get last signal level transmitted to or received from the specified port.
2068 */
2069PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
2070 unsigned *tx_level,
2071 unsigned *rx_level)
2072{
2073 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
2074 tx_level, rx_level);
2075}
2076
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002077/*****************************************************************************
2078 * File player.
2079 */
2080
Benny Prijonod5696da2007-07-17 16:25:45 +00002081static char* get_basename(const char *path, unsigned len)
2082{
2083 char *p = ((char*)path) + len;
2084
2085 if (len==0)
2086 return p;
2087
Benny Prijono1f61a8f2007-08-16 10:11:44 +00002088 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00002089
2090 return (p==path) ? p : p+1;
2091}
2092
2093
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002094/*
2095 * Create a file player, and automatically connect this player to
2096 * the conference bridge.
2097 */
2098PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
2099 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002100 pjsua_player_id *p_id)
2101{
2102 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002103 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00002104 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002105 pjmedia_port *port;
2106 pj_status_t status;
2107
2108 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2109 return PJ_ETOOMANY;
2110
2111 PJSUA_LOCK();
2112
2113 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2114 if (pjsua_var.player[file_id].port == NULL)
2115 break;
2116 }
2117
2118 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2119 /* This is unexpected */
2120 PJSUA_UNLOCK();
2121 pj_assert(0);
2122 return PJ_EBUG;
2123 }
2124
2125 pj_memcpy(path, filename->ptr, filename->slen);
2126 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00002127
2128 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2129 if (!pool) {
2130 PJSUA_UNLOCK();
2131 return PJ_ENOMEM;
2132 }
2133
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00002134 status = pjmedia_wav_player_port_create(
2135 pool, path,
2136 pjsua_var.mconf_cfg.samples_per_frame *
2137 1000 / pjsua_var.media_cfg.channel_count /
2138 pjsua_var.media_cfg.clock_rate,
2139 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002140 if (status != PJ_SUCCESS) {
2141 PJSUA_UNLOCK();
2142 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002143 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002144 return status;
2145 }
2146
Benny Prijono5297af92008-03-18 13:40:40 +00002147 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002148 port, filename, &slot);
2149 if (status != PJ_SUCCESS) {
2150 pjmedia_port_destroy(port);
2151 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00002152 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
2153 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002154 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002155 return status;
2156 }
2157
Benny Prijonoa66c3312007-01-21 23:12:40 +00002158 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00002159 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002160 pjsua_var.player[file_id].port = port;
2161 pjsua_var.player[file_id].slot = slot;
2162
2163 if (p_id) *p_id = file_id;
2164
2165 ++pjsua_var.player_cnt;
2166
2167 PJSUA_UNLOCK();
2168 return PJ_SUCCESS;
2169}
2170
2171
2172/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00002173 * Create a file playlist media port, and automatically add the port
2174 * to the conference bridge.
2175 */
2176PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
2177 unsigned file_count,
2178 const pj_str_t *label,
2179 unsigned options,
2180 pjsua_player_id *p_id)
2181{
2182 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00002183 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002184 pjmedia_port *port;
2185 pj_status_t status;
2186
2187 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2188 return PJ_ETOOMANY;
2189
2190 PJSUA_LOCK();
2191
2192 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2193 if (pjsua_var.player[file_id].port == NULL)
2194 break;
2195 }
2196
2197 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2198 /* This is unexpected */
2199 PJSUA_UNLOCK();
2200 pj_assert(0);
2201 return PJ_EBUG;
2202 }
2203
2204
2205 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
2206 pjsua_var.media_cfg.clock_rate;
2207
Benny Prijonod5696da2007-07-17 16:25:45 +00002208 pool = pjsua_pool_create("playlist", 1000, 1000);
2209 if (!pool) {
2210 PJSUA_UNLOCK();
2211 return PJ_ENOMEM;
2212 }
2213
2214 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002215 file_names, file_count,
2216 ptime, options, 0, &port);
2217 if (status != PJ_SUCCESS) {
2218 PJSUA_UNLOCK();
2219 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002220 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002221 return status;
2222 }
2223
Benny Prijonod5696da2007-07-17 16:25:45 +00002224 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002225 port, &port->info.name, &slot);
2226 if (status != PJ_SUCCESS) {
2227 pjmedia_port_destroy(port);
2228 PJSUA_UNLOCK();
2229 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002230 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002231 return status;
2232 }
2233
2234 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00002235 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002236 pjsua_var.player[file_id].port = port;
2237 pjsua_var.player[file_id].slot = slot;
2238
2239 if (p_id) *p_id = file_id;
2240
2241 ++pjsua_var.player_cnt;
2242
2243 PJSUA_UNLOCK();
2244 return PJ_SUCCESS;
2245
2246}
2247
2248
2249/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002250 * Get conference port ID associated with player.
2251 */
2252PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
2253{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002254 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002255 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2256
2257 return pjsua_var.player[id].slot;
2258}
2259
Benny Prijono469b1522006-12-26 03:05:17 +00002260/*
2261 * Get the media port for the player.
2262 */
Benny Prijonobe41d862008-01-18 13:24:28 +00002263PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00002264 pjmedia_port **p_port)
2265{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002266 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002267 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2268 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2269
2270 *p_port = pjsua_var.player[id].port;
2271
2272 return PJ_SUCCESS;
2273}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002274
2275/*
2276 * Set playback position.
2277 */
2278PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
2279 pj_uint32_t samples)
2280{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002281 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002282 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002283 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002284
2285 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
2286}
2287
2288
2289/*
2290 * Close the file, remove the player from the bridge, and free
2291 * resources associated with the file player.
2292 */
2293PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
2294{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002295 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002296 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2297
2298 PJSUA_LOCK();
2299
2300 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002301 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002302 pjmedia_port_destroy(pjsua_var.player[id].port);
2303 pjsua_var.player[id].port = NULL;
2304 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002305 pj_pool_release(pjsua_var.player[id].pool);
2306 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002307 pjsua_var.player_cnt--;
2308 }
2309
2310 PJSUA_UNLOCK();
2311
2312 return PJ_SUCCESS;
2313}
2314
2315
2316/*****************************************************************************
2317 * File recorder.
2318 */
2319
2320/*
2321 * Create a file recorder, and automatically connect this recorder to
2322 * the conference bridge.
2323 */
2324PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00002325 unsigned enc_type,
2326 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002327 pj_ssize_t max_size,
2328 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002329 pjsua_recorder_id *p_id)
2330{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002331 enum Format
2332 {
2333 FMT_UNKNOWN,
2334 FMT_WAV,
2335 FMT_MP3,
2336 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002337 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002338 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002339 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00002340 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00002341 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002342 pjmedia_port *port;
2343 pj_status_t status;
2344
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002345 /* Filename must present */
2346 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
2347
Benny Prijono00cae612006-07-31 15:19:36 +00002348 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002349 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002350
Benny Prijono8f310522006-10-20 11:08:49 +00002351 /* Don't support encoding type at present */
2352 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002353
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002354 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
2355 return PJ_ETOOMANY;
2356
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002357 /* Determine the file format */
2358 ext.ptr = filename->ptr + filename->slen - 4;
2359 ext.slen = 4;
2360
2361 if (pj_stricmp2(&ext, ".wav") == 0)
2362 file_format = FMT_WAV;
2363 else if (pj_stricmp2(&ext, ".mp3") == 0)
2364 file_format = FMT_MP3;
2365 else {
2366 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
2367 "determine file format for %.*s",
2368 (int)filename->slen, filename->ptr));
2369 return PJ_ENOTSUP;
2370 }
2371
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002372 PJSUA_LOCK();
2373
2374 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
2375 if (pjsua_var.recorder[file_id].port == NULL)
2376 break;
2377 }
2378
2379 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
2380 /* This is unexpected */
2381 PJSUA_UNLOCK();
2382 pj_assert(0);
2383 return PJ_EBUG;
2384 }
2385
2386 pj_memcpy(path, filename->ptr, filename->slen);
2387 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002388
Benny Prijonod5696da2007-07-17 16:25:45 +00002389 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2390 if (!pool) {
2391 PJSUA_UNLOCK();
2392 return PJ_ENOMEM;
2393 }
2394
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002395 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00002396 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002397 pjsua_var.media_cfg.clock_rate,
2398 pjsua_var.mconf_cfg.channel_count,
2399 pjsua_var.mconf_cfg.samples_per_frame,
2400 pjsua_var.mconf_cfg.bits_per_sample,
2401 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002402 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00002403 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002404 port = NULL;
2405 status = PJ_ENOTSUP;
2406 }
2407
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002408 if (status != PJ_SUCCESS) {
2409 PJSUA_UNLOCK();
2410 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002411 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002412 return status;
2413 }
2414
Benny Prijonod5696da2007-07-17 16:25:45 +00002415 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002416 port, filename, &slot);
2417 if (status != PJ_SUCCESS) {
2418 pjmedia_port_destroy(port);
2419 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00002420 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002421 return status;
2422 }
2423
2424 pjsua_var.recorder[file_id].port = port;
2425 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00002426 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002427
2428 if (p_id) *p_id = file_id;
2429
2430 ++pjsua_var.rec_cnt;
2431
2432 PJSUA_UNLOCK();
2433 return PJ_SUCCESS;
2434}
2435
2436
2437/*
2438 * Get conference port associated with recorder.
2439 */
2440PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
2441{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002442 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2443 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002444 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2445
2446 return pjsua_var.recorder[id].slot;
2447}
2448
Benny Prijono469b1522006-12-26 03:05:17 +00002449/*
2450 * Get the media port for the recorder.
2451 */
2452PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
2453 pjmedia_port **p_port)
2454{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002455 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2456 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002457 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2458 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2459
2460 *p_port = pjsua_var.recorder[id].port;
2461 return PJ_SUCCESS;
2462}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002463
2464/*
2465 * Destroy recorder (this will complete recording).
2466 */
2467PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
2468{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002469 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2470 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002471 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2472
2473 PJSUA_LOCK();
2474
2475 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002476 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002477 pjmedia_port_destroy(pjsua_var.recorder[id].port);
2478 pjsua_var.recorder[id].port = NULL;
2479 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002480 pj_pool_release(pjsua_var.recorder[id].pool);
2481 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002482 pjsua_var.rec_cnt--;
2483 }
2484
2485 PJSUA_UNLOCK();
2486
2487 return PJ_SUCCESS;
2488}
2489
2490
2491/*****************************************************************************
2492 * Sound devices.
2493 */
2494
2495/*
2496 * Enum sound devices.
2497 */
Benny Prijonof798e502009-03-09 13:08:16 +00002498
2499PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002500 unsigned *count)
2501{
2502 unsigned i, dev_count;
2503
Benny Prijono10454dc2009-02-21 14:21:59 +00002504 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002505
2506 if (dev_count > *count) dev_count = *count;
2507
2508 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00002509 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002510
Benny Prijono10454dc2009-02-21 14:21:59 +00002511 status = pjmedia_aud_dev_get_info(i, &info[i]);
2512 if (status != PJ_SUCCESS)
2513 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002514 }
2515
2516 *count = dev_count;
2517
2518 return PJ_SUCCESS;
2519}
Benny Prijonof798e502009-03-09 13:08:16 +00002520
2521
Benny Prijono10454dc2009-02-21 14:21:59 +00002522PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
2523 unsigned *count)
2524{
2525 unsigned i, dev_count;
2526
2527 dev_count = pjmedia_aud_dev_count();
2528
2529 if (dev_count > *count) dev_count = *count;
2530 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
2531
2532 for (i=0; i<dev_count; ++i) {
2533 pjmedia_aud_dev_info ai;
2534 pj_status_t status;
2535
2536 status = pjmedia_aud_dev_get_info(i, &ai);
2537 if (status != PJ_SUCCESS)
2538 return status;
2539
2540 strncpy(info[i].name, ai.name, sizeof(info[i].name));
2541 info[i].name[sizeof(info[i].name)-1] = '\0';
2542 info[i].input_count = ai.input_count;
2543 info[i].output_count = ai.output_count;
2544 info[i].default_samples_per_sec = ai.default_samples_per_sec;
2545 }
2546
2547 *count = dev_count;
2548
2549 return PJ_SUCCESS;
2550}
Benny Prijono10454dc2009-02-21 14:21:59 +00002551
Benny Prijonof798e502009-03-09 13:08:16 +00002552/* Create audio device parameter to open the device */
2553static pj_status_t create_aud_param(pjmedia_aud_param *param,
2554 pjmedia_aud_dev_index capture_dev,
2555 pjmedia_aud_dev_index playback_dev,
2556 unsigned clock_rate,
2557 unsigned channel_count,
2558 unsigned samples_per_frame,
2559 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002560{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002561 pj_status_t status;
2562
Benny Prijono96e74f32009-02-22 12:00:12 +00002563 /* Normalize device ID with new convention about default device ID */
2564 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
2565 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
2566
Benny Prijono10454dc2009-02-21 14:21:59 +00002567 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00002568 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00002569 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00002570 pjsua_perror(THIS_FILE, "Error retrieving default audio "
2571 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00002572 return status;
2573 }
Benny Prijonof798e502009-03-09 13:08:16 +00002574 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
2575 param->rec_id = capture_dev;
2576 param->play_id = playback_dev;
2577 param->clock_rate = clock_rate;
2578 param->channel_count = channel_count;
2579 param->samples_per_frame = samples_per_frame;
2580 param->bits_per_sample = bits_per_sample;
2581
2582 /* Update the setting with user preference */
2583#define update_param(cap, field) \
2584 if (pjsua_var.aud_param.flags & cap) { \
2585 param->flags |= cap; \
2586 param->field = pjsua_var.aud_param.field; \
2587 }
2588 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
2589 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
2590 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
2591 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
2592#undef update_param
2593
Benny Prijono10454dc2009-02-21 14:21:59 +00002594 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00002595 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
2596 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
2597 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
2598 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
2599
Benny Prijono10454dc2009-02-21 14:21:59 +00002600 /* EC settings */
2601 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00002602 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
2603 param->ec_enabled = PJ_TRUE;
2604 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00002605 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00002606 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00002607 }
2608
Benny Prijonof798e502009-03-09 13:08:16 +00002609 return PJ_SUCCESS;
2610}
Benny Prijono26056d82006-10-11 16:03:41 +00002611
Benny Prijonof798e502009-03-09 13:08:16 +00002612/* Internal: the first time the audio device is opened (during app
2613 * startup), retrieve the audio settings such as volume level
2614 * so that aud_get_settings() will work.
2615 */
2616static pj_status_t update_initial_aud_param()
2617{
2618 pjmedia_aud_stream *strm;
2619 pjmedia_aud_param param;
2620 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00002621
Benny Prijonof798e502009-03-09 13:08:16 +00002622 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002623
Benny Prijonof798e502009-03-09 13:08:16 +00002624 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00002625
Benny Prijonof798e502009-03-09 13:08:16 +00002626 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002627 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00002628 pjsua_perror(THIS_FILE, "Error audio stream "
2629 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002630 return status;
2631 }
2632
Benny Prijonof798e502009-03-09 13:08:16 +00002633#define update_saved_param(cap, field) \
2634 if (param.flags & cap) { \
2635 pjsua_var.aud_param.flags |= cap; \
2636 pjsua_var.aud_param.field = param.field; \
2637 }
2638
2639 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
2640 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
2641 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
2642 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
2643#undef update_saved_param
2644
2645 return PJ_SUCCESS;
2646}
2647
2648/* Get format name */
2649static const char *get_fmt_name(pj_uint32_t id)
2650{
2651 static char name[8];
2652
2653 if (id == PJMEDIA_FORMAT_L16)
2654 return "PCM";
2655 pj_memcpy(name, &id, 4);
2656 name[4] = '\0';
2657 return name;
2658}
2659
2660/* Open sound device with the setting. */
2661static pj_status_t open_snd_dev(pjmedia_aud_param *param)
2662{
2663 pjmedia_port *conf_port;
2664 pj_status_t status;
2665
2666 PJ_ASSERT_RETURN(param, PJ_EINVAL);
2667
2668 /* Check if NULL sound device is used */
2669 if (NULL_SND_DEV_ID==param->rec_id || NULL_SND_DEV_ID==param->play_id) {
2670 return pjsua_set_null_snd_dev();
2671 }
2672
2673 /* Close existing sound port */
2674 close_snd_dev();
2675
2676 /* Create memory pool for sound device. */
2677 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2678 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2679
2680
2681 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
2682 get_fmt_name(param->ext_fmt.id),
2683 param->clock_rate, param->channel_count,
2684 param->samples_per_frame / param->channel_count * 1000 /
2685 param->clock_rate));
2686
2687 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
2688 param, &pjsua_var.snd_port);
2689 if (status != PJ_SUCCESS)
2690 return status;
2691
2692 /* Get the port0 of the conference bridge. */
2693 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2694 pj_assert(conf_port != NULL);
2695
2696 /* For conference bridge, resample if necessary if the bridge's
2697 * clock rate is different than the sound device's clock rate.
2698 */
2699 if (!pjsua_var.is_mswitch &&
2700 param->ext_fmt.id == PJMEDIA_FORMAT_PCM &&
2701 conf_port->info.clock_rate != param->clock_rate)
2702 {
2703 pjmedia_port *resample_port;
2704 unsigned resample_opt = 0;
2705
2706 if (pjsua_var.media_cfg.quality >= 3 &&
2707 pjsua_var.media_cfg.quality <= 4)
2708 {
2709 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
2710 }
2711 else if (pjsua_var.media_cfg.quality < 3) {
2712 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
2713 }
2714
2715 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
2716 conf_port,
2717 param->clock_rate,
2718 resample_opt,
2719 &resample_port);
2720 if (status != PJ_SUCCESS) {
2721 char errmsg[PJ_ERR_MSG_SIZE];
2722 pj_strerror(status, errmsg, sizeof(errmsg));
2723 PJ_LOG(4, (THIS_FILE,
2724 "Error creating resample port: %s",
2725 errmsg));
2726 close_snd_dev();
2727 return status;
2728 }
2729
2730 conf_port = resample_port;
2731 }
2732
2733 /* Otherwise for audio switchboard, the switch's port0 setting is
2734 * derived from the sound device setting, so update the setting.
2735 */
2736 if (pjsua_var.is_mswitch) {
2737 pj_memcpy(&conf_port->info.format, &param->ext_fmt,
2738 sizeof(conf_port->info.format));
2739 conf_port->info.clock_rate = param->clock_rate;
2740 conf_port->info.samples_per_frame = param->samples_per_frame;
2741 conf_port->info.channel_count = param->channel_count;
2742 conf_port->info.bits_per_sample = 16;
2743 }
2744
2745 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00002746 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
2747 conf_port );
2748 if (status != PJ_SUCCESS) {
2749 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
2750 "sound device", status);
2751 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2752 pjsua_var.snd_port = NULL;
2753 return status;
2754 }
2755
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002756 /* Save the device IDs */
Benny Prijonof798e502009-03-09 13:08:16 +00002757 pjsua_var.cap_dev = param->rec_id;
2758 pjsua_var.play_dev = param->play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002759
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002760 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00002761 {
2762 pjmedia_aud_dev_info rec_info;
2763 pjmedia_aud_stream *strm;
2764 pjmedia_aud_param si;
2765 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002766
Benny Prijonof798e502009-03-09 13:08:16 +00002767 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2768 status = pjmedia_aud_stream_get_param(strm, &si);
2769 if (status == PJ_SUCCESS)
2770 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00002771
Benny Prijonof798e502009-03-09 13:08:16 +00002772 if (status==PJ_SUCCESS) {
2773 if (param->clock_rate != pjsua_var.media_cfg.clock_rate) {
2774 char tmp_buf[128];
2775 int tmp_buf_len = sizeof(tmp_buf);
2776
2777 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
2778 "%s (%dKHz)",
2779 rec_info.name,
2780 param->clock_rate/1000);
2781 pj_strset(&tmp, tmp_buf, tmp_buf_len);
2782 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
2783 } else {
2784 pjmedia_conf_set_port0_name(pjsua_var.mconf,
2785 pj_cstr(&tmp, rec_info.name));
2786 }
2787 }
2788
2789 /* Any error is not major, let it through */
2790 status = PJ_SUCCESS;
2791 };
2792
2793 /* If this is the first time the audio device is open, retrieve some
2794 * settings from the device (such as volume settings) so that the
2795 * pjsua_snd_get_setting() work.
2796 */
2797 if (pjsua_var.aud_open_cnt == 0) {
2798 update_initial_aud_param();
2799 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00002800 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002801
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002802 return PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00002803}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002804
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002805
Benny Prijonof798e502009-03-09 13:08:16 +00002806/* Close existing sound device */
2807static void close_snd_dev(void)
2808{
2809 /* Close sound device */
2810 if (pjsua_var.snd_port) {
2811 pjmedia_aud_dev_info cap_info, play_info;
2812 pjmedia_aud_stream *strm;
2813 pjmedia_aud_param param;
2814
2815 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2816 pjmedia_aud_stream_get_param(strm, &param);
2817
2818 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
2819 cap_info.name[0] = '\0';
2820 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
2821 play_info.name[0] = '\0';
2822
2823 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
2824 "%s sound capture device",
2825 play_info.name, cap_info.name));
2826
2827 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
2828 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2829 pjsua_var.snd_port = NULL;
2830 }
2831
2832 /* Close null sound device */
2833 if (pjsua_var.null_snd) {
2834 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
2835 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
2836 pjsua_var.null_snd = NULL;
2837 }
2838
2839 if (pjsua_var.snd_pool)
2840 pj_pool_release(pjsua_var.snd_pool);
2841 pjsua_var.snd_pool = NULL;
2842}
2843
2844
2845/*
2846 * Select or change sound device. Application may call this function at
2847 * any time to replace current sound device.
2848 */
2849PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
2850 int playback_dev)
2851{
2852 unsigned alt_cr_cnt = 1;
2853 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
2854 unsigned i;
2855 pj_status_t status = -1;
2856
Benny Prijono23ea21a2009-06-03 12:43:06 +00002857 /* Null-sound */
2858 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
2859 return pjsua_set_null_snd_dev();
2860 }
2861
Benny Prijonof798e502009-03-09 13:08:16 +00002862 /* Set default clock rate */
2863 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
2864 if (alt_cr[0] == 0)
2865 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
2866
2867 /* Allow retrying of different clock rate if we're using conference
2868 * bridge (meaning audio format is always PCM), otherwise lock on
2869 * to one clock rate.
2870 */
2871 if (pjsua_var.is_mswitch) {
2872 alt_cr_cnt = 1;
2873 } else {
2874 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
2875 }
2876
2877 /* Attempts to open the sound device with different clock rates */
2878 for (i=0; i<alt_cr_cnt; ++i) {
2879 pjmedia_aud_param param;
2880 unsigned samples_per_frame;
2881
2882 /* Create the default audio param */
2883 samples_per_frame = alt_cr[i] *
2884 pjsua_var.media_cfg.audio_frame_ptime *
2885 pjsua_var.media_cfg.channel_count / 1000;
2886 status = create_aud_param(&param, capture_dev, playback_dev,
2887 alt_cr[i], pjsua_var.media_cfg.channel_count,
2888 samples_per_frame, 16);
2889 if (status != PJ_SUCCESS)
2890 return status;
2891
2892 /* Open! */
2893 status = open_snd_dev(&param);
2894 if (status == PJ_SUCCESS)
2895 break;
2896 }
2897
2898 if (status != PJ_SUCCESS) {
2899 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
2900 return status;
2901 }
2902
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00002903 pjsua_var.no_snd = PJ_FALSE;
2904
Benny Prijonof798e502009-03-09 13:08:16 +00002905 return PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002906}
2907
2908
2909/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00002910 * Get currently active sound devices. If sound devices has not been created
2911 * (for example when pjsua_start() is not called), it is possible that
2912 * the function returns PJ_SUCCESS with -1 as device IDs.
2913 */
2914PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
2915 int *playback_dev)
2916{
2917 if (capture_dev) {
2918 *capture_dev = pjsua_var.cap_dev;
2919 }
2920 if (playback_dev) {
2921 *playback_dev = pjsua_var.play_dev;
2922 }
2923
2924 return PJ_SUCCESS;
2925}
2926
2927
2928/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002929 * Use null sound device.
2930 */
2931PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
2932{
2933 pjmedia_port *conf_port;
2934 pj_status_t status;
2935
2936 /* Close existing sound device */
2937 close_snd_dev();
2938
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002939 /* Create memory pool for sound device. */
2940 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2941 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2942
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002943 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
2944
2945 /* Get the port0 of the conference bridge. */
2946 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2947 pj_assert(conf_port != NULL);
2948
2949 /* Create master port, connecting port0 of the conference bridge to
2950 * a null port.
2951 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002952 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002953 conf_port, 0, &pjsua_var.null_snd);
2954 if (status != PJ_SUCCESS) {
2955 pjsua_perror(THIS_FILE, "Unable to create null sound device",
2956 status);
2957 return status;
2958 }
2959
2960 /* Start the master port */
2961 status = pjmedia_master_port_start(pjsua_var.null_snd);
2962 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2963
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002964 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2965 pjsua_var.play_dev = NULL_SND_DEV_ID;
2966
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00002967 pjsua_var.no_snd = PJ_FALSE;
2968
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002969 return PJ_SUCCESS;
2970}
2971
2972
Benny Prijonoe909eac2006-07-27 22:04:56 +00002973
2974/*
2975 * Use no device!
2976 */
2977PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2978{
2979 /* Close existing sound device */
2980 close_snd_dev();
2981
2982 pjsua_var.no_snd = PJ_TRUE;
2983 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2984}
2985
2986
Benny Prijonof20687a2006-08-04 18:27:19 +00002987/*
2988 * Configure the AEC settings of the sound port.
2989 */
Benny Prijono5da50432006-08-07 10:24:52 +00002990PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00002991{
2992 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2993
2994 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00002995 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
2996 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00002997
2998 return PJ_SUCCESS;
2999}
3000
3001
3002/*
3003 * Get current AEC tail length.
3004 */
Benny Prijono22dfe592006-08-06 12:07:13 +00003005PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00003006{
3007 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
3008 return PJ_SUCCESS;
3009}
3010
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00003011
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003012/*
Benny Prijonof798e502009-03-09 13:08:16 +00003013 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003014 */
Benny Prijonof798e502009-03-09 13:08:16 +00003015PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003016{
Benny Prijonof798e502009-03-09 13:08:16 +00003017 return pjsua_var.snd_port != NULL;
3018}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003019
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003020
Benny Prijonof798e502009-03-09 13:08:16 +00003021/*
3022 * Configure sound device setting to the sound device being used.
3023 */
3024PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
3025 const void *pval,
3026 pj_bool_t keep)
3027{
Benny Prijono09b0ff62009-03-10 12:07:51 +00003028 pj_status_t status;
3029
Benny Prijonof798e502009-03-09 13:08:16 +00003030 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003031 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00003032 return PJMEDIA_EAUD_INVCAP;
3033 }
3034
Benny Prijono09b0ff62009-03-10 12:07:51 +00003035 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00003036 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00003037 pjmedia_aud_stream *strm;
3038
3039 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003040 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00003041 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00003042 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00003043 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00003044
3045 if (status != PJ_SUCCESS)
3046 return status;
3047
3048 /* Save in internal param for later device open */
3049 if (keep) {
3050 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
3051 cap, pval);
3052 }
3053
3054 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00003055}
3056
3057/*
3058 * Retrieve a sound device setting.
3059 */
3060PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
3061 void *pval)
3062{
3063 /* If sound device has never been opened before, open it to
3064 * retrieve the initial setting from the device (e.g. audio
3065 * volume)
3066 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003067 if (pjsua_var.aud_open_cnt==0) {
3068 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00003069 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003070 close_snd_dev();
3071 }
Benny Prijonof798e502009-03-09 13:08:16 +00003072
3073 if (pjsua_snd_is_active()) {
3074 /* Sound is active, retrieve from device directly */
3075 pjmedia_aud_stream *strm;
3076
3077 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3078 return pjmedia_aud_stream_get_cap(strm, cap, pval);
3079 } else {
3080 /* Otherwise retrieve from internal param */
3081 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
3082 cap, pval);
3083 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003084}
3085
3086
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003087/*****************************************************************************
3088 * Codecs.
3089 */
3090
3091/*
3092 * Enum all supported codecs in the system.
3093 */
3094PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
3095 unsigned *p_count )
3096{
3097 pjmedia_codec_mgr *codec_mgr;
3098 pjmedia_codec_info info[32];
3099 unsigned i, count, prio[32];
3100 pj_status_t status;
3101
3102 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3103 count = PJ_ARRAY_SIZE(info);
3104 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
3105 if (status != PJ_SUCCESS) {
3106 *p_count = 0;
3107 return status;
3108 }
3109
3110 if (count > *p_count) count = *p_count;
3111
3112 for (i=0; i<count; ++i) {
3113 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
3114 id[i].codec_id = pj_str(id[i].buf_);
3115 id[i].priority = (pj_uint8_t) prio[i];
3116 }
3117
3118 *p_count = count;
3119
3120 return PJ_SUCCESS;
3121}
3122
3123
3124/*
3125 * Change codec priority.
3126 */
3127PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
3128 pj_uint8_t priority )
3129{
Benny Prijono88accae2008-06-26 15:48:14 +00003130 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003131 pjmedia_codec_mgr *codec_mgr;
3132
3133 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3134
Benny Prijono88accae2008-06-26 15:48:14 +00003135 if (codec_id->slen==1 && *codec_id->ptr=='*')
3136 codec_id = &all;
3137
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003138 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
3139 priority);
3140}
3141
3142
3143/*
3144 * Get codec parameters.
3145 */
3146PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
3147 pjmedia_codec_param *param )
3148{
Benny Prijono88accae2008-06-26 15:48:14 +00003149 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003150 const pjmedia_codec_info *info;
3151 pjmedia_codec_mgr *codec_mgr;
3152 unsigned count = 1;
3153 pj_status_t status;
3154
3155 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3156
Benny Prijono88accae2008-06-26 15:48:14 +00003157 if (codec_id->slen==1 && *codec_id->ptr=='*')
3158 codec_id = &all;
3159
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003160 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
3161 &count, &info, NULL);
3162 if (status != PJ_SUCCESS)
3163 return status;
3164
3165 if (count != 1)
3166 return PJ_ENOTFOUND;
3167
3168 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
3169 return status;
3170}
3171
3172
3173/*
3174 * Set codec parameters.
3175 */
Nanang Izzuddin06839e72010-01-27 11:48:31 +00003176PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003177 const pjmedia_codec_param *param)
3178{
Nanang Izzuddin06839e72010-01-27 11:48:31 +00003179 const pjmedia_codec_info *info[2];
3180 pjmedia_codec_mgr *codec_mgr;
3181 unsigned count = 2;
3182 pj_status_t status;
3183
3184 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3185
3186 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
3187 &count, info, NULL);
3188 if (status != PJ_SUCCESS)
3189 return status;
3190
3191 /* Codec ID should be specific, except for G.722.1 */
3192 if (count > 1 &&
3193 pj_strnicmp2(codec_id, "G7221/16", 8) != 0 &&
3194 pj_strnicmp2(codec_id, "G7221/32", 8) != 0)
3195 {
3196 pj_assert(!"Codec ID is not specific");
3197 return PJ_ETOOMANY;
3198 }
3199
3200 status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param);
3201 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003202}