blob: 01f33a1cde5f137d6ae08f10f6e38eb84bc6d22e [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 Prijono6ba8c542007-10-16 01:34:14 +0000337 /* Perform NAT detection */
338 pjsua_detect_nat_type();
339
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000340 return PJ_SUCCESS;
341}
342
343
344/*
345 * Create RTP and RTCP socket pair, and possibly resolve their public
346 * address via STUN.
347 */
348static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
349 pjmedia_sock_info *skinfo)
350{
351 enum {
352 RTP_RETRY = 100
353 };
354 int i;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000355 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000356 pj_sockaddr_in mapped_addr[2];
357 pj_status_t status = PJ_SUCCESS;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000358 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000359 pj_sock_t sock[2];
360
Benny Prijonoc97608e2007-03-23 16:34:20 +0000361 /* Make sure STUN server resolution has completed */
Benny Prijonobb995fd2009-08-12 11:03:23 +0000362 status = resolve_stun_server(PJ_TRUE);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000363 if (status != PJ_SUCCESS) {
364 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
365 return status;
366 }
367
Benny Prijonode479562007-03-15 10:23:55 +0000368 if (next_rtp_port == 0)
369 next_rtp_port = (pj_uint16_t)cfg->port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000370
371 for (i=0; i<2; ++i)
372 sock[i] = PJ_INVALID_SOCKET;
373
Benny Prijono0a5cad82006-09-26 13:21:02 +0000374 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
375 if (cfg->bound_addr.slen) {
376 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
377 if (status != PJ_SUCCESS) {
378 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
379 status);
380 return status;
381 }
382 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000383
384 /* Loop retry to bind RTP and RTCP sockets. */
Benny Prijonode479562007-03-15 10:23:55 +0000385 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000386
387 /* Create and bind RTP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000388 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000389 if (status != PJ_SUCCESS) {
390 pjsua_perror(THIS_FILE, "socket() error", status);
391 return status;
392 }
393
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000394 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
395 next_rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000396 if (status != PJ_SUCCESS) {
397 pj_sock_close(sock[0]);
398 sock[0] = PJ_INVALID_SOCKET;
399 continue;
400 }
401
402 /* Create and bind RTCP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000403 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000404 if (status != PJ_SUCCESS) {
405 pjsua_perror(THIS_FILE, "socket() error", status);
406 pj_sock_close(sock[0]);
407 return status;
408 }
409
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000410 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
411 (pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000412 if (status != PJ_SUCCESS) {
413 pj_sock_close(sock[0]);
414 sock[0] = PJ_INVALID_SOCKET;
415
416 pj_sock_close(sock[1]);
417 sock[1] = PJ_INVALID_SOCKET;
418 continue;
419 }
420
421 /*
422 * If we're configured to use STUN, then find out the mapped address,
423 * and make sure that the mapped RTCP port is adjacent with the RTP.
424 */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000425 if (pjsua_var.stun_srv.addr.sa_family != 0) {
426 char ip_addr[32];
427 pj_str_t stun_srv;
428
429 pj_ansi_strcpy(ip_addr,
430 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
431 stun_srv = pj_str(ip_addr);
432
Benny Prijono14c2b862007-02-21 00:40:05 +0000433 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000434 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
435 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000436 mapped_addr);
437 if (status != PJ_SUCCESS) {
438 pjsua_perror(THIS_FILE, "STUN resolve error", status);
439 goto on_error;
440 }
441
Benny Prijono80eee892007-11-03 22:43:23 +0000442#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000443 if (pj_ntohs(mapped_addr[1].sin_port) ==
444 pj_ntohs(mapped_addr[0].sin_port)+1)
445 {
446 /* Success! */
447 break;
448 }
449
450 pj_sock_close(sock[0]);
451 sock[0] = PJ_INVALID_SOCKET;
452
453 pj_sock_close(sock[1]);
454 sock[1] = PJ_INVALID_SOCKET;
Benny Prijono80eee892007-11-03 22:43:23 +0000455#else
456 if (pj_ntohs(mapped_addr[1].sin_port) !=
457 pj_ntohs(mapped_addr[0].sin_port)+1)
458 {
459 PJ_LOG(4,(THIS_FILE,
460 "Note: STUN mapped RTCP port %d is not adjacent"
461 " to RTP port %d",
462 pj_ntohs(mapped_addr[1].sin_port),
463 pj_ntohs(mapped_addr[0].sin_port)));
464 }
465 /* Success! */
466 break;
467#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000468
Benny Prijono0a5cad82006-09-26 13:21:02 +0000469 } else if (cfg->public_addr.slen) {
470
471 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000472 (pj_uint16_t)next_rtp_port);
Benny Prijono0a5cad82006-09-26 13:21:02 +0000473 if (status != PJ_SUCCESS)
474 goto on_error;
475
476 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000477 (pj_uint16_t)(next_rtp_port+1));
Benny Prijono0a5cad82006-09-26 13:21:02 +0000478 if (status != PJ_SUCCESS)
479 goto on_error;
480
481 break;
482
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000483 } else {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000484
Benny Prijono42d08d22007-12-20 11:23:07 +0000485 if (bound_addr.sin_addr.s_addr == 0) {
486 pj_sockaddr addr;
487
488 /* Get local IP address. */
489 status = pj_gethostip(pj_AF_INET(), &addr);
490 if (status != PJ_SUCCESS)
491 goto on_error;
492
493 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
494 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000495
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000496 for (i=0; i<2; ++i) {
497 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
Benny Prijono42d08d22007-12-20 11:23:07 +0000498 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000499 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000500
Benny Prijonode479562007-03-15 10:23:55 +0000501 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
502 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000503 break;
504 }
505 }
506
507 if (sock[0] == PJ_INVALID_SOCKET) {
508 PJ_LOG(1,(THIS_FILE,
509 "Unable to find appropriate RTP/RTCP ports combination"));
510 goto on_error;
511 }
512
513
514 skinfo->rtp_sock = sock[0];
515 pj_memcpy(&skinfo->rtp_addr_name,
516 &mapped_addr[0], sizeof(pj_sockaddr_in));
517
518 skinfo->rtcp_sock = sock[1];
519 pj_memcpy(&skinfo->rtcp_addr_name,
520 &mapped_addr[1], sizeof(pj_sockaddr_in));
521
Benny Prijono8b22ce12008-02-08 12:57:55 +0000522 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000523 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
524 sizeof(addr_buf), 3)));
Benny Prijono8b22ce12008-02-08 12:57:55 +0000525 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000526 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
527 sizeof(addr_buf), 3)));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000528
Benny Prijonode479562007-03-15 10:23:55 +0000529 next_rtp_port += 2;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000530 return PJ_SUCCESS;
531
532on_error:
533 for (i=0; i<2; ++i) {
534 if (sock[i] != PJ_INVALID_SOCKET)
535 pj_sock_close(sock[i]);
536 }
537 return status;
538}
539
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000540/* Check if sound device is idle. */
541static void check_snd_dev_idle()
542{
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000543 unsigned call_cnt;
544
545 /* Get the call count, we shouldn't close the sound device when there is
546 * any calls active.
547 */
548 call_cnt = pjsua_call_get_count();
549
550 /* When this function is called from pjsua_media_channel_deinit() upon
551 * disconnecting call, actually the call count hasn't been updated/
552 * decreased. So we put additional check here, if there is only one
553 * call and it's in DISCONNECTED state, there is actually no active
554 * call.
555 */
556 if (call_cnt == 1) {
557 pjsua_call_id call_id;
558 pj_status_t status;
559
560 status = pjsua_enum_calls(&call_id, &call_cnt);
561 if (status == PJ_SUCCESS && call_cnt > 0 &&
562 !pjsua_call_is_active(call_id))
563 {
564 call_cnt = 0;
565 }
566 }
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000567
568 /* Activate sound device auto-close timer if sound device is idle.
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000569 * It is idle when there is no port connection in the bridge and
570 * there is no active call.
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000571 */
572 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) &&
573 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
574 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000575 call_cnt == 0 &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000576 pjsua_var.media_cfg.snd_auto_close_time >= 0)
577 {
578 pj_time_val delay;
579
580 delay.msec = 0;
581 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
582
583 pjsua_var.snd_idle_timer.id = PJ_TRUE;
584 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
585 &delay);
586 }
587}
588
589
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000590/* Timer callback to close sound device */
591static void close_snd_timer_cb( pj_timer_heap_t *th,
592 pj_timer_entry *entry)
593{
594 PJ_UNUSED_ARG(th);
595
Benny Prijono0f711b42009-05-06 19:08:43 +0000596 PJSUA_LOCK();
597 if (entry->id) {
598 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
599 pjsua_var.media_cfg.snd_auto_close_time));
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000600
Benny Prijono0f711b42009-05-06 19:08:43 +0000601 entry->id = PJ_FALSE;
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000602
Benny Prijono0f711b42009-05-06 19:08:43 +0000603 close_snd_dev();
604 }
605 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000606}
607
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000608
609/*
610 * Start pjsua media subsystem.
611 */
612pj_status_t pjsua_media_subsys_start(void)
613{
614 pj_status_t status;
615
616 /* Create media for calls, if none is specified */
617 if (pjsua_var.calls[0].med_tp == NULL) {
618 pjsua_transport_config transport_cfg;
619
620 /* Create default transport config */
621 pjsua_transport_config_default(&transport_cfg);
622 transport_cfg.port = DEFAULT_RTP_PORT;
623
624 status = pjsua_media_transports_create(&transport_cfg);
625 if (status != PJ_SUCCESS)
626 return status;
627 }
628
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000629 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
630 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000631
632 return PJ_SUCCESS;
633}
634
635
636/*
637 * Destroy pjsua media subsystem.
638 */
639pj_status_t pjsua_media_subsys_destroy(void)
640{
641 unsigned i;
642
Benny Prijono384dab42009-10-14 01:58:04 +0000643 PJ_LOG(4,(THIS_FILE, "Shutting down media.."));
644
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000645 close_snd_dev();
646
647 if (pjsua_var.mconf) {
648 pjmedia_conf_destroy(pjsua_var.mconf);
649 pjsua_var.mconf = NULL;
650 }
651
652 if (pjsua_var.null_port) {
653 pjmedia_port_destroy(pjsua_var.null_port);
654 pjsua_var.null_port = NULL;
655 }
656
657 /* Destroy file players */
658 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
659 if (pjsua_var.player[i].port) {
660 pjmedia_port_destroy(pjsua_var.player[i].port);
661 pjsua_var.player[i].port = NULL;
662 }
663 }
664
665 /* Destroy file recorders */
666 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
667 if (pjsua_var.recorder[i].port) {
668 pjmedia_port_destroy(pjsua_var.recorder[i].port);
669 pjsua_var.recorder[i].port = NULL;
670 }
671 }
672
673 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000674 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono311b63f2008-07-14 11:31:40 +0000675 if (pjsua_var.calls[i].med_tp_st != PJSUA_MED_TP_IDLE) {
676 pjsua_media_channel_deinit(i);
677 }
Benny Prijono40860c32008-09-04 13:55:33 +0000678 if (pjsua_var.calls[i].med_tp && pjsua_var.calls[i].med_tp_auto_del) {
679 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000680 }
Benny Prijono40860c32008-09-04 13:55:33 +0000681 pjsua_var.calls[i].med_tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000682 }
683
684 /* Destroy media endpoint. */
685 if (pjsua_var.med_endpt) {
686
687 /* Shutdown all codecs: */
688# if PJMEDIA_HAS_SPEEX_CODEC
689 pjmedia_codec_speex_deinit();
690# endif /* PJMEDIA_HAS_SPEEX_CODEC */
691
692# if PJMEDIA_HAS_GSM_CODEC
693 pjmedia_codec_gsm_deinit();
694# endif /* PJMEDIA_HAS_GSM_CODEC */
695
696# if PJMEDIA_HAS_G711_CODEC
697 pjmedia_codec_g711_deinit();
698# endif /* PJMEDIA_HAS_G711_CODEC */
699
Benny Prijono7ffd7752008-03-17 14:07:53 +0000700# if PJMEDIA_HAS_G722_CODEC
701 pjmedia_codec_g722_deinit();
702# endif /* PJMEDIA_HAS_G722_CODEC */
703
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000704# if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000705 pjmedia_codec_ipp_deinit();
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000706# endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000707
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000708# if PJMEDIA_HAS_PASSTHROUGH_CODECS
709 pjmedia_codec_passthrough_deinit();
710# endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
711
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000712# if PJMEDIA_HAS_G7221_CODEC
713 pjmedia_codec_g7221_deinit();
714# endif /* PJMEDIA_HAS_G7221_CODEC */
715
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000716# if PJMEDIA_HAS_L16_CODEC
717 pjmedia_codec_l16_deinit();
718# endif /* PJMEDIA_HAS_L16_CODEC */
719
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000720 pjmedia_endpt_destroy(pjsua_var.med_endpt);
721 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000722
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000723 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000724 // Not necessary, as pjmedia_snd_deinit() should have been called
725 // in pjmedia_endpt_destroy().
726 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000727 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000728
Benny Prijonode479562007-03-15 10:23:55 +0000729 /* Reset RTP port */
730 next_rtp_port = 0;
731
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000732 return PJ_SUCCESS;
733}
734
735
Benny Prijonoc97608e2007-03-23 16:34:20 +0000736/* Create normal UDP media transports */
737static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000738{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000739 unsigned i;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000740 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000741 pj_status_t status;
742
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000743 /* Create each media transport */
744 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
745
Benny Prijono617c5bc2007-04-02 19:51:21 +0000746 status = create_rtp_rtcp_sock(cfg, &skinfo);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000747 if (status != PJ_SUCCESS) {
748 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
749 status);
750 goto on_error;
751 }
Benny Prijonod8179652008-01-23 20:39:07 +0000752
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000753 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000754 &skinfo, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000755 &pjsua_var.calls[i].med_tp);
756 if (status != PJ_SUCCESS) {
757 pjsua_perror(THIS_FILE, "Unable to create media transport",
758 status);
759 goto on_error;
760 }
Benny Prijono00cae612006-07-31 15:19:36 +0000761
Benny Prijonod8179652008-01-23 20:39:07 +0000762 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
763 PJMEDIA_DIR_ENCODING,
764 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000765
Benny Prijonod8179652008-01-23 20:39:07 +0000766 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
767 PJMEDIA_DIR_DECODING,
768 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000769
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000770 }
771
Benny Prijonoc97608e2007-03-23 16:34:20 +0000772 return PJ_SUCCESS;
773
774on_error:
775 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
776 if (pjsua_var.calls[i].med_tp != NULL) {
777 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
778 pjsua_var.calls[i].med_tp = NULL;
779 }
780 }
781
782 return status;
783}
784
785
Benny Prijono096c56c2007-09-15 08:30:16 +0000786/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000787static void on_ice_complete(pjmedia_transport *tp,
788 pj_ice_strans_op op,
789 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000790{
Benny Prijonof76e1392008-06-06 14:51:48 +0000791 unsigned id;
Benny Prijono096c56c2007-09-15 08:30:16 +0000792 pj_bool_t found = PJ_FALSE;
793
Benny Prijono096c56c2007-09-15 08:30:16 +0000794 /* Find call which has this media transport */
795
796 PJSUA_LOCK();
797
Benny Prijonof76e1392008-06-06 14:51:48 +0000798 for (id=0; id<pjsua_var.ua_cfg.max_calls; ++id) {
799 if (pjsua_var.calls[id].med_tp == tp ||
800 pjsua_var.calls[id].med_orig == tp)
801 {
802 found = PJ_TRUE;
803 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000804 }
805 }
806
807 PJSUA_UNLOCK();
808
Benny Prijonof76e1392008-06-06 14:51:48 +0000809 if (!found)
810 return;
811
812 switch (op) {
813 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono224b4e22008-06-19 14:10:28 +0000814 pjsua_var.calls[id].med_tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000815 break;
816 case PJ_ICE_STRANS_OP_NEGOTIATION:
817 if (result != PJ_SUCCESS) {
818 pjsua_var.calls[id].media_st = PJSUA_CALL_MEDIA_ERROR;
819 pjsua_var.calls[id].media_dir = PJMEDIA_DIR_NONE;
820
821 if (pjsua_var.ua_cfg.cb.on_call_media_state) {
822 pjsua_var.ua_cfg.cb.on_call_media_state(id);
823 }
824 }
825 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000826 }
827}
828
829
Benny Prijonof76e1392008-06-06 14:51:48 +0000830/* Parse "HOST:PORT" format */
831static pj_status_t parse_host_port(const pj_str_t *host_port,
832 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000833{
Benny Prijonof76e1392008-06-06 14:51:48 +0000834 pj_str_t str_port;
835
836 str_port.ptr = pj_strchr(host_port, ':');
837 if (str_port.ptr != NULL) {
838 int iport;
839
840 host->ptr = host_port->ptr;
841 host->slen = (str_port.ptr - host->ptr);
842 str_port.ptr++;
843 str_port.slen = host_port->slen - host->slen - 1;
844 iport = (int)pj_strtoul(&str_port);
845 if (iport < 1 || iport > 65535)
846 return PJ_EINVAL;
847 *port = (pj_uint16_t)iport;
848 } else {
849 *host = *host_port;
850 *port = 0;
851 }
852
853 return PJ_SUCCESS;
854}
855
856/* Create ICE media transports (when ice is enabled) */
857static pj_status_t create_ice_media_transports(void)
858{
859 char stunip[PJ_INET6_ADDRSTRLEN];
860 pj_ice_strans_cfg ice_cfg;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000861 unsigned i;
862 pj_status_t status;
863
Benny Prijonoda9785b2007-04-02 20:43:06 +0000864 /* Make sure STUN server resolution has completed */
Benny Prijonobb995fd2009-08-12 11:03:23 +0000865 status = resolve_stun_server(PJ_TRUE);
Benny Prijonoda9785b2007-04-02 20:43:06 +0000866 if (status != PJ_SUCCESS) {
867 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
868 return status;
869 }
870
Benny Prijonof76e1392008-06-06 14:51:48 +0000871 /* Create ICE stream transport configuration */
872 pj_ice_strans_cfg_default(&ice_cfg);
873 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
874 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
875 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
876
877 ice_cfg.af = pj_AF_INET();
878 ice_cfg.resolver = pjsua_var.resolver;
879
Benny Prijono329d6382009-05-29 13:04:03 +0000880 ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
881
Benny Prijonof76e1392008-06-06 14:51:48 +0000882 /* Configure STUN settings */
883 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
884 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
885 ice_cfg.stun.server = pj_str(stunip);
886 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
887 }
Benny Prijono329d6382009-05-29 13:04:03 +0000888 if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
889 ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
Benny Prijonof76e1392008-06-06 14:51:48 +0000890
891 /* Configure TURN settings */
892 if (pjsua_var.media_cfg.enable_turn) {
893 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
894 &ice_cfg.turn.server,
895 &ice_cfg.turn.port);
896 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
897 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
898 return PJ_EINVAL;
899 }
900 if (ice_cfg.turn.port == 0)
901 ice_cfg.turn.port = 3479;
902 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
903 pj_memcpy(&ice_cfg.turn.auth_cred,
904 &pjsua_var.media_cfg.turn_auth_cred,
905 sizeof(ice_cfg.turn.auth_cred));
906 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000907
Benny Prijonoc97608e2007-03-23 16:34:20 +0000908 /* Create each media transport */
909 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono096c56c2007-09-15 08:30:16 +0000910 pjmedia_ice_cb ice_cb;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000911 char name[32];
Benny Prijonof76e1392008-06-06 14:51:48 +0000912 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000913
Benny Prijono096c56c2007-09-15 08:30:16 +0000914 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
915 ice_cb.on_ice_complete = &on_ice_complete;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000916 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i);
Benny Prijono224b4e22008-06-19 14:10:28 +0000917 pjsua_var.calls[i].med_tp_ready = PJ_EPENDING;
Benny Prijonof76e1392008-06-06 14:51:48 +0000918
919 comp_cnt = 1;
Benny Prijono551af422008-08-07 09:55:52 +0000920 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
Benny Prijonof76e1392008-06-06 14:51:48 +0000921 ++comp_cnt;
922
923 status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
924 &ice_cfg, &ice_cb,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000925 &pjsua_var.calls[i].med_tp);
926 if (status != PJ_SUCCESS) {
927 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
928 status);
929 goto on_error;
930 }
931
Benny Prijonof76e1392008-06-06 14:51:48 +0000932 /* Wait until transport is initialized, or time out */
933 PJSUA_UNLOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000934 while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000935 pjsua_handle_events(100);
936 }
937 PJSUA_LOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000938 if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000939 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
Benny Prijono224b4e22008-06-19 14:10:28 +0000940 pjsua_var.calls[i].med_tp_ready);
941 status = pjsua_var.calls[i].med_tp_ready;
Benny Prijonof76e1392008-06-06 14:51:48 +0000942 goto on_error;
943 }
944
Benny Prijonod8179652008-01-23 20:39:07 +0000945 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
946 PJMEDIA_DIR_ENCODING,
947 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono11da9bc2007-09-15 08:55:00 +0000948
Benny Prijonod8179652008-01-23 20:39:07 +0000949 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
950 PJMEDIA_DIR_DECODING,
951 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000952 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000953
954 return PJ_SUCCESS;
955
956on_error:
957 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
958 if (pjsua_var.calls[i].med_tp != NULL) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000959 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000960 pjsua_var.calls[i].med_tp = NULL;
961 }
962 }
963
Benny Prijonoc97608e2007-03-23 16:34:20 +0000964 return status;
965}
966
967
968/*
969 * Create UDP media transports for all the calls. This function creates
970 * one UDP media transport for each call.
971 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +0000972PJ_DEF(pj_status_t) pjsua_media_transports_create(
973 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000974{
975 pjsua_transport_config cfg;
976 unsigned i;
977 pj_status_t status;
978
979
980 /* Make sure pjsua_init() has been called */
981 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
982
983 PJSUA_LOCK();
984
985 /* Delete existing media transports */
986 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono40860c32008-09-04 13:55:33 +0000987 if (pjsua_var.calls[i].med_tp != NULL &&
988 pjsua_var.calls[i].med_tp_auto_del)
989 {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000990 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
991 pjsua_var.calls[i].med_tp = NULL;
Nanang Izzuddind704a8b2008-09-23 16:34:07 +0000992 pjsua_var.calls[i].med_orig = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000993 }
994 }
995
996 /* Copy config */
997 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
998
Benny Prijono40860c32008-09-04 13:55:33 +0000999 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001000 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijonof76e1392008-06-06 14:51:48 +00001001 status = create_ice_media_transports();
Benny Prijonoc97608e2007-03-23 16:34:20 +00001002 } else {
1003 status = create_udp_media_transports(&cfg);
1004 }
1005
Benny Prijono40860c32008-09-04 13:55:33 +00001006 /* Set media transport auto_delete to True */
1007 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
1008 pjsua_var.calls[i].med_tp_auto_del = PJ_TRUE;
1009 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001010
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001011 PJSUA_UNLOCK();
1012
1013 return status;
1014}
1015
Benny Prijono40860c32008-09-04 13:55:33 +00001016/*
1017 * Attach application's created media transports.
1018 */
1019PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
1020 unsigned count,
1021 pj_bool_t auto_delete)
1022{
1023 unsigned i;
1024
1025 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
1026
1027 /* Assign the media transports */
1028 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
1029 if (pjsua_var.calls[i].med_tp != NULL &&
1030 pjsua_var.calls[i].med_tp_auto_del)
1031 {
1032 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
1033 }
1034
1035 pjsua_var.calls[i].med_tp = tp[i].transport;
1036 pjsua_var.calls[i].med_tp_auto_del = auto_delete;
1037 }
1038
1039 return PJ_SUCCESS;
1040}
1041
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001042
Benny Prijonoc97608e2007-03-23 16:34:20 +00001043pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
Benny Prijonod8179652008-01-23 20:39:07 +00001044 pjsip_role_e role,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001045 int security_level,
Benny Prijono224b4e22008-06-19 14:10:28 +00001046 pj_pool_t *tmp_pool,
1047 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001048 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001049{
1050 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono224b4e22008-06-19 14:10:28 +00001051 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001052
Benny Prijonod8179652008-01-23 20:39:07 +00001053#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1054 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
1055 pjmedia_srtp_setting srtp_opt;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001056 pjmedia_transport *srtp = NULL;
Benny Prijonod8179652008-01-23 20:39:07 +00001057#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +00001058
Benny Prijonod8179652008-01-23 20:39:07 +00001059 PJ_UNUSED_ARG(role);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001060
Benny Prijonod8179652008-01-23 20:39:07 +00001061 /* Return error if media transport has not been created yet
1062 * (e.g. application is starting)
1063 */
1064 if (call->med_tp == NULL) {
Benny Prijono03789052008-09-16 14:30:50 +00001065 if (sip_err_code)
1066 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
Benny Prijonod8179652008-01-23 20:39:07 +00001067 return PJ_EBUSY;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001068 }
1069
Benny Prijonod8179652008-01-23 20:39:07 +00001070#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Benny Prijono53a7c702008-04-14 02:57:29 +00001071 /* This function may be called when SRTP transport already exists
1072 * (e.g: in re-invite, update), don't need to destroy/re-create.
1073 */
1074 if (!call->med_orig || call->med_tp == call->med_orig) {
1075
1076 /* Check if SRTP requires secure signaling */
1077 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
1078 if (security_level < acc->cfg.srtp_secure_signaling) {
1079 if (sip_err_code)
1080 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1081 return PJSIP_ESESSIONINSECURE;
1082 }
Benny Prijonod8179652008-01-23 20:39:07 +00001083 }
Benny Prijonod8179652008-01-23 20:39:07 +00001084
Benny Prijono53a7c702008-04-14 02:57:29 +00001085 /* Always create SRTP adapter */
1086 pjmedia_srtp_setting_default(&srtp_opt);
1087 srtp_opt.close_member_tp = PJ_FALSE;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001088 /* If media session has been ever established, let's use remote's
1089 * preference in SRTP usage policy, especially when it is stricter.
1090 */
1091 if (call->rem_srtp_use > acc->cfg.use_srtp)
1092 srtp_opt.use = call->rem_srtp_use;
1093 else
1094 srtp_opt.use = acc->cfg.use_srtp;
1095
Benny Prijono53a7c702008-04-14 02:57:29 +00001096 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
1097 call->med_tp,
1098 &srtp_opt, &srtp);
1099 if (status != PJ_SUCCESS) {
1100 if (sip_err_code)
1101 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
1102 return status;
1103 }
Benny Prijonod8179652008-01-23 20:39:07 +00001104
Benny Prijono53a7c702008-04-14 02:57:29 +00001105 /* Set SRTP as current media transport */
1106 call->med_orig = call->med_tp;
1107 call->med_tp = srtp;
1108 }
Benny Prijonod8179652008-01-23 20:39:07 +00001109#else
1110 call->med_orig = call->med_tp;
1111 PJ_UNUSED_ARG(security_level);
1112#endif
1113
Benny Prijonoa310bd22008-06-27 21:19:44 +00001114 /* Find out which media line in SDP that we support. If we are offerer,
1115 * audio will be at index 0 in SDP.
1116 */
1117 if (rem_sdp == 0) {
1118 call->audio_idx = 0;
1119 }
1120 /* Otherwise find out the candidate audio media line in SDP */
1121 else {
1122 unsigned i;
1123 pj_bool_t srtp_active;
1124
1125#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1126 srtp_active = acc->cfg.use_srtp && srtp != NULL;
1127#else
1128 srtp_active = PJ_FALSE;
1129#endif
1130
1131 /* Media count must have been checked */
1132 pj_assert(rem_sdp->media_count != 0);
1133
1134 for (i=0; i<rem_sdp->media_count; ++i) {
1135 const pjmedia_sdp_media *m = rem_sdp->media[i];
1136
1137 /* Skip if media is not audio */
1138 if (pj_stricmp2(&m->desc.media, "audio") != 0)
1139 continue;
1140
1141 /* Skip if media is disabled */
1142 if (m->desc.port == 0)
1143 continue;
1144
1145 /* Skip if transport is not supported */
1146 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") != 0 &&
1147 pj_stricmp2(&m->desc.transport, "RTP/SAVP") != 0)
1148 {
1149 continue;
1150 }
1151
1152 if (call->audio_idx == -1) {
1153 call->audio_idx = i;
1154 } else {
1155 /* We've found multiple candidates. This could happen
1156 * e.g. when remote is offering both RTP/AVP and RTP/AVP,
1157 * or when remote for some reason offers two audio.
1158 */
1159
1160 if (srtp_active &&
1161 pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0)
1162 {
1163 /* Prefer RTP/SAVP when our media transport is SRTP */
1164 call->audio_idx = i;
1165 } else if (!srtp_active &&
1166 pj_stricmp2(&m->desc.transport, "RTP/AVP")==0)
1167 {
1168 /* Prefer RTP/AVP when our media transport is NOT SRTP */
1169 call->audio_idx = i;
1170 }
1171 }
1172 }
1173 }
1174
1175 /* Reject offer if we couldn't find a good m=audio line in offer */
1176 if (call->audio_idx < 0) {
Benny Prijonoab8dba92008-06-27 21:59:15 +00001177 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001178 pjsua_media_channel_deinit(call_id);
Benny Prijonoab8dba92008-06-27 21:59:15 +00001179 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001180 }
1181
1182 PJ_LOG(4,(THIS_FILE, "Media index %d selected for call %d",
1183 call->audio_idx, call->index));
1184
Benny Prijono224b4e22008-06-19 14:10:28 +00001185 /* Create the media transport */
1186 status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001187 rem_sdp, call->audio_idx);
Benny Prijono224b4e22008-06-19 14:10:28 +00001188 if (status != PJ_SUCCESS) {
1189 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1190 pjsua_media_channel_deinit(call_id);
1191 return status;
1192 }
1193
1194 call->med_tp_st = PJSUA_MED_TP_INIT;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001195 return PJ_SUCCESS;
1196}
1197
1198pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1199 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001200 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001201 pjmedia_sdp_session **p_sdp,
1202 int *sip_status_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001203{
Benny Prijonoa310bd22008-06-27 21:19:44 +00001204 enum { MAX_MEDIA = 1 };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001205 pjmedia_sdp_session *sdp;
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001206 pjmedia_transport_info tpinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001207 pjsua_call *call = &pjsua_var.calls[call_id];
1208 pj_status_t status;
1209
Benny Prijono55e82352007-05-10 20:49:08 +00001210 /* Return error if media transport has not been created yet
1211 * (e.g. application is starting)
1212 */
1213 if (call->med_tp == NULL) {
1214 return PJ_EBUSY;
1215 }
1216
Benny Prijonoa310bd22008-06-27 21:19:44 +00001217 /* Media index must have been determined before */
1218 pj_assert(call->audio_idx != -1);
1219
Benny Prijono224b4e22008-06-19 14:10:28 +00001220 /* Create media if it's not created. This could happen when call is
1221 * currently on-hold
1222 */
1223 if (call->med_tp_st == PJSUA_MED_TP_IDLE) {
1224 pjsip_role_e role;
1225 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1226 status = pjsua_media_channel_init(call_id, role, call->secure_level,
1227 pool, rem_sdp, sip_status_code);
1228 if (status != PJ_SUCCESS)
1229 return status;
1230 }
1231
Benny Prijono617c5bc2007-04-02 19:51:21 +00001232 /* Get media socket info */
Benny Prijono734fc2d2008-03-17 16:05:35 +00001233 pjmedia_transport_info_init(&tpinfo);
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001234 pjmedia_transport_get_info(call->med_tp, &tpinfo);
Benny Prijono617c5bc2007-04-02 19:51:21 +00001235
1236 /* Create SDP */
Benny Prijonod8179652008-01-23 20:39:07 +00001237 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA,
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001238 &tpinfo.sock_info, &sdp);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001239 if (status != PJ_SUCCESS) {
1240 if (sip_status_code) *sip_status_code = 500;
Benny Prijono224b4e22008-06-19 14:10:28 +00001241 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001242 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001243
Benny Prijonoa310bd22008-06-27 21:19:44 +00001244 /* If we're answering and the selected media is not the first media
1245 * in SDP, then fill in the unselected media with with zero port.
1246 * Otherwise we'll crash in transport_encode_sdp() because the media
1247 * lines are not aligned between offer and answer.
1248 */
1249 if (rem_sdp && call->audio_idx != 0) {
1250 unsigned i;
1251
1252 for (i=0; i<rem_sdp->media_count; ++i) {
1253 const pjmedia_sdp_media *rem_m = rem_sdp->media[i];
1254 pjmedia_sdp_media *m;
1255 const pjmedia_sdp_attr *a;
1256
1257 if ((int)i == call->audio_idx)
1258 continue;
1259
1260 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1261 pj_strdup(pool, &m->desc.media, &rem_m->desc.media);
1262 pj_strdup(pool, &m->desc.transport, &rem_m->desc.transport);
1263 m->desc.port = 0;
1264
1265 /* Add one format, copy from the offer. And copy the corresponding
1266 * rtpmap and fmtp attributes too.
1267 */
1268 m->desc.fmt_count = 1;
1269 pj_strdup(pool, &m->desc.fmt[0], &rem_m->desc.fmt[0]);
1270 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1271 "rtpmap", &m->desc.fmt[0])) != NULL)
1272 {
1273 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1274 }
1275 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1276 "fmtp", &m->desc.fmt[0])) != NULL)
1277 {
1278 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1279 }
1280
1281 if (i==sdp->media_count)
1282 sdp->media[sdp->media_count++] = m;
1283 else {
1284 pj_array_insert(sdp->media, sizeof(sdp->media[0]),
1285 sdp->media_count, i, &m);
1286 ++sdp->media_count;
1287 }
1288 }
1289 }
1290
Benny Prijono6ba8c542007-10-16 01:34:14 +00001291 /* Add NAT info in the SDP */
1292 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1293 pjmedia_sdp_attr *a;
1294 pj_str_t value;
1295 char nat_info[80];
1296
1297 value.ptr = nat_info;
1298 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1299 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1300 "%d", pjsua_var.nat_type);
1301 } else {
1302 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1303 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1304 "%d %s",
1305 pjsua_var.nat_type,
1306 type_name);
1307 }
1308
1309 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1310
1311 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1312
1313 }
1314
Benny Prijonod8179652008-01-23 20:39:07 +00001315 /* Give the SDP to media transport */
Benny Prijono224b4e22008-06-19 14:10:28 +00001316 status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001317 call->audio_idx);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001318 if (status != PJ_SUCCESS) {
1319 if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono224b4e22008-06-19 14:10:28 +00001320 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001321 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001322
1323 *p_sdp = sdp;
1324 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001325}
1326
1327
1328static void stop_media_session(pjsua_call_id call_id)
1329{
1330 pjsua_call *call = &pjsua_var.calls[call_id];
1331
1332 if (call->conf_slot != PJSUA_INVALID_ID) {
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001333 if (pjsua_var.mconf) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001334 pjsua_conf_remove_port(call->conf_slot);
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001335 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001336 call->conf_slot = PJSUA_INVALID_ID;
1337 }
1338
1339 if (call->session) {
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001340 pjmedia_rtcp_stat stat;
1341
Nanang Izzuddin437d77c2008-08-26 18:04:15 +00001342 if (pjmedia_session_get_stream_stat(call->session, 0, &stat)
1343 == PJ_SUCCESS)
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001344 {
1345 /* Save RTP timestamp & sequence, so when media session is
1346 * restarted, those values will be restored as the initial
1347 * RTP timestamp & sequence of the new media session. So in
1348 * the same call session, RTP timestamp and sequence are
1349 * guaranteed to be contigue.
1350 */
1351 call->rtp_tx_seq_ts_set = 1 | (1 << 1);
1352 call->rtp_tx_seq = stat.rtp_tx_last_seq;
1353 call->rtp_tx_ts = stat.rtp_tx_last_ts;
1354 }
1355
Benny Prijonofc13bf62008-02-20 08:56:15 +00001356 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1357 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0);
1358 }
1359
Benny Prijonoc97608e2007-03-23 16:34:20 +00001360 pjmedia_session_destroy(call->session);
1361 call->session = NULL;
1362
1363 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
1364 call_id));
1365
1366 }
1367
1368 call->media_st = PJSUA_CALL_MEDIA_NONE;
1369}
1370
1371pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1372{
1373 pjsua_call *call = &pjsua_var.calls[call_id];
1374
1375 stop_media_session(call_id);
1376
Benny Prijono224b4e22008-06-19 14:10:28 +00001377 if (call->med_tp_st != PJSUA_MED_TP_IDLE) {
1378 pjmedia_transport_media_stop(call->med_tp);
1379 call->med_tp_st = PJSUA_MED_TP_IDLE;
1380 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001381
Benny Prijono311b63f2008-07-14 11:31:40 +00001382 if (call->med_orig && call->med_tp && call->med_tp != call->med_orig) {
Benny Prijonod8179652008-01-23 20:39:07 +00001383 pjmedia_transport_close(call->med_tp);
1384 call->med_tp = call->med_orig;
1385 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00001386
1387 check_snd_dev_idle();
1388
Benny Prijonoc97608e2007-03-23 16:34:20 +00001389 return PJ_SUCCESS;
1390}
1391
1392
1393/*
1394 * DTMF callback from the stream.
1395 */
1396static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1397 int digit)
1398{
1399 PJ_UNUSED_ARG(strm);
1400
Benny Prijono0c068262008-02-14 14:38:52 +00001401 /* For discussions about call mutex protection related to this
1402 * callback, please see ticket #460:
1403 * http://trac.pjsip.org/repos/ticket/460#comment:4
1404 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001405 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1406 pjsua_call_id call_id;
1407
Benny Prijonod8179652008-01-23 20:39:07 +00001408 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001409 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1410 }
1411}
1412
1413
1414pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijono224b4e22008-06-19 14:10:28 +00001415 const pjmedia_sdp_session *local_sdp,
Benny Prijonodbce2cf2007-03-28 16:24:00 +00001416 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001417{
1418 int prev_media_st = 0;
1419 pjsua_call *call = &pjsua_var.calls[call_id];
1420 pjmedia_session_info sess_info;
Benny Prijono91e567e2007-12-28 08:51:58 +00001421 pjmedia_stream_info *si = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001422 pjmedia_port *media_port;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001423 pj_status_t status;
1424
1425 /* Destroy existing media session, if any. */
1426 prev_media_st = call->media_st;
1427 stop_media_session(call->index);
1428
1429 /* Create media session info based on SDP parameters.
Benny Prijonoc97608e2007-03-23 16:34:20 +00001430 */
Benny Prijono40d62b62009-08-12 17:53:47 +00001431 status = pjmedia_session_info_from_sdp( call->inv->pool_prov,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001432 pjsua_var.med_endpt,
Benny Prijono91e567e2007-12-28 08:51:58 +00001433 PJMEDIA_MAX_SDP_MEDIA, &sess_info,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001434 local_sdp, remote_sdp);
1435 if (status != PJ_SUCCESS)
1436 return status;
1437
Benny Prijonoa310bd22008-06-27 21:19:44 +00001438 /* Find which session is audio */
1439 PJ_ASSERT_RETURN(call->audio_idx != -1, PJ_EBUG);
1440 PJ_ASSERT_RETURN(call->audio_idx < (int)sess_info.stream_cnt, PJ_EBUG);
1441 si = &sess_info.stream_info[call->audio_idx];
Benny Prijono91e567e2007-12-28 08:51:58 +00001442
1443 /* Reset session info with only one media stream */
1444 sess_info.stream_cnt = 1;
1445 if (si != &sess_info.stream_info[0])
1446 pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001447
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001448 /* Check if no media is active */
Benny Prijono91e567e2007-12-28 08:51:58 +00001449 if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001450 {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001451 /* Call media state */
1452 call->media_st = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001453
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001454 /* Call media direction */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001455 call->media_dir = PJMEDIA_DIR_NONE;
1456
Benny Prijonod8179652008-01-23 20:39:07 +00001457 /* Shutdown transport's session */
1458 pjmedia_transport_media_stop(call->med_tp);
Benny Prijono224b4e22008-06-19 14:10:28 +00001459 call->med_tp_st = PJSUA_MED_TP_IDLE;
Benny Prijono667952e2007-04-02 19:27:54 +00001460
Benny Prijonoc97608e2007-03-23 16:34:20 +00001461 /* No need because we need keepalive? */
1462
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001463 /* Close upper entry of transport stack */
1464 if (call->med_orig && (call->med_tp != call->med_orig)) {
1465 pjmedia_transport_close(call->med_tp);
1466 call->med_tp = call->med_orig;
1467 }
1468
Benny Prijonoc97608e2007-03-23 16:34:20 +00001469 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001470 pjmedia_transport_info tp_info;
1471
Benny Prijono224b4e22008-06-19 14:10:28 +00001472 /* Start/restart media transport */
Benny Prijonod8179652008-01-23 20:39:07 +00001473 status = pjmedia_transport_media_start(call->med_tp,
Benny Prijono40d62b62009-08-12 17:53:47 +00001474 call->inv->pool_prov,
Benny Prijonod8179652008-01-23 20:39:07 +00001475 local_sdp, remote_sdp, 0);
1476 if (status != PJ_SUCCESS)
1477 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001478
Benny Prijono224b4e22008-06-19 14:10:28 +00001479 call->med_tp_st = PJSUA_MED_TP_RUNNING;
1480
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001481 /* Get remote SRTP usage policy */
1482 pjmedia_transport_info_init(&tp_info);
1483 pjmedia_transport_get_info(call->med_tp, &tp_info);
1484 if (tp_info.specific_info_cnt > 0) {
1485 int i;
1486 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1487 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1488 {
1489 pjmedia_srtp_info *srtp_info =
1490 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1491
1492 call->rem_srtp_use = srtp_info->peer_use;
1493 break;
1494 }
1495 }
1496 }
1497
Benny Prijonoc97608e2007-03-23 16:34:20 +00001498 /* Override ptime, if this option is specified. */
1499 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001500 si->param->setting.frm_per_pkt = (pj_uint8_t)
1501 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1502 if (si->param->setting.frm_per_pkt == 0)
1503 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001504 }
1505
1506 /* Disable VAD, if this option is specified. */
1507 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001508 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001509 }
1510
1511
1512 /* Optionally, application may modify other stream settings here
1513 * (such as jitter buffer parameters, codec ptime, etc.)
1514 */
Benny Prijono91e567e2007-12-28 08:51:58 +00001515 si->jb_init = pjsua_var.media_cfg.jb_init;
1516 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1517 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1518 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001519
Benny Prijono8147f402007-11-21 14:50:07 +00001520 /* Set SSRC */
Benny Prijono91e567e2007-12-28 08:51:58 +00001521 si->ssrc = call->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00001522
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001523 /* Set RTP timestamp & sequence, normally these value are intialized
1524 * automatically when stream session created, but for some cases (e.g:
1525 * call reinvite, call update) timestamp and sequence need to be kept
1526 * contigue.
1527 */
1528 si->rtp_ts = call->rtp_tx_ts;
1529 si->rtp_seq = call->rtp_tx_seq;
1530 si->rtp_seq_ts_set = call->rtp_tx_seq_ts_set;
1531
Benny Prijonoc97608e2007-03-23 16:34:20 +00001532 /* Create session based on session info. */
1533 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
1534 &call->med_tp,
1535 call, &call->session );
1536 if (status != PJ_SUCCESS) {
1537 return status;
1538 }
1539
1540 /* If DTMF callback is installed by application, install our
1541 * callback to the session.
1542 */
1543 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1544 pjmedia_session_set_dtmf_callback(call->session, 0,
1545 &dtmf_callback,
Benny Prijonod8179652008-01-23 20:39:07 +00001546 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001547 }
1548
1549 /* Get the port interface of the first stream in the session.
1550 * We need the port interface to add to the conference bridge.
1551 */
1552 pjmedia_session_get_port(call->session, 0, &media_port);
1553
Benny Prijonofc13bf62008-02-20 08:56:15 +00001554 /* Notify application about stream creation.
1555 * Note: application may modify media_port to point to different
1556 * media port
1557 */
1558 if (pjsua_var.ua_cfg.cb.on_stream_created) {
1559 pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session,
1560 0, &media_port);
1561 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001562
1563 /*
1564 * Add the call to conference bridge.
1565 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001566 {
1567 char tmp[PJSIP_MAX_URL_SIZE];
1568 pj_str_t port_name;
1569
1570 port_name.ptr = tmp;
1571 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1572 call->inv->dlg->remote.info->uri,
1573 tmp, sizeof(tmp));
1574 if (port_name.slen < 1) {
1575 port_name = pj_str("call");
1576 }
Benny Prijono40d62b62009-08-12 17:53:47 +00001577 status = pjmedia_conf_add_port( pjsua_var.mconf,
1578 call->inv->pool_prov,
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001579 media_port,
1580 &port_name,
1581 (unsigned*)&call->conf_slot);
1582 if (status != PJ_SUCCESS) {
1583 return status;
1584 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001585 }
1586
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001587 /* Call media direction */
Benny Prijono91e567e2007-12-28 08:51:58 +00001588 call->media_dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001589
1590 /* Call media state */
1591 if (call->local_hold)
1592 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
1593 else if (call->media_dir == PJMEDIA_DIR_DECODING)
1594 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
1595 else
1596 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001597 }
1598
1599 /* Print info. */
1600 {
1601 char info[80];
1602 int info_len = 0;
1603 unsigned i;
1604
1605 for (i=0; i<sess_info.stream_cnt; ++i) {
1606 int len;
1607 const char *dir;
1608 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1609
1610 switch (strm_info->dir) {
1611 case PJMEDIA_DIR_NONE:
1612 dir = "inactive";
1613 break;
1614 case PJMEDIA_DIR_ENCODING:
1615 dir = "sendonly";
1616 break;
1617 case PJMEDIA_DIR_DECODING:
1618 dir = "recvonly";
1619 break;
1620 case PJMEDIA_DIR_ENCODING_DECODING:
1621 dir = "sendrecv";
1622 break;
1623 default:
1624 dir = "unknown";
1625 break;
1626 }
1627 len = pj_ansi_sprintf( info+info_len,
1628 ", stream #%d: %.*s (%s)", i,
1629 (int)strm_info->fmt.encoding_name.slen,
1630 strm_info->fmt.encoding_name.ptr,
1631 dir);
1632 if (len > 0)
1633 info_len += len;
1634 }
1635 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1636 }
1637
1638 return PJ_SUCCESS;
1639}
1640
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001641/*
1642 * Get maxinum number of conference ports.
1643 */
1644PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1645{
1646 return pjsua_var.media_cfg.max_media_ports;
1647}
1648
1649
1650/*
1651 * Get current number of active ports in the bridge.
1652 */
1653PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1654{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001655 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001656 unsigned count = PJ_ARRAY_SIZE(ports);
1657 pj_status_t status;
1658
1659 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1660 if (status != PJ_SUCCESS)
1661 count = 0;
1662
1663 return count;
1664}
1665
1666
1667/*
1668 * Enumerate all conference ports.
1669 */
1670PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1671 unsigned *count)
1672{
1673 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1674}
1675
1676
1677/*
1678 * Get information about the specified conference port
1679 */
1680PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1681 pjsua_conf_port_info *info)
1682{
1683 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001684 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001685 pj_status_t status;
1686
1687 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1688 if (status != PJ_SUCCESS)
1689 return status;
1690
Benny Prijonoac623b32006-07-03 15:19:31 +00001691 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001692 info->slot_id = id;
1693 info->name = cinfo.name;
1694 info->clock_rate = cinfo.clock_rate;
1695 info->channel_count = cinfo.channel_count;
1696 info->samples_per_frame = cinfo.samples_per_frame;
1697 info->bits_per_sample = cinfo.bits_per_sample;
1698
1699 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001700 info->listener_cnt = cinfo.listener_cnt;
1701 for (i=0; i<cinfo.listener_cnt; ++i) {
1702 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001703 }
1704
1705 return PJ_SUCCESS;
1706}
1707
1708
1709/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001710 * Add arbitrary media port to PJSUA's conference bridge.
1711 */
1712PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1713 pjmedia_port *port,
1714 pjsua_conf_port_id *p_id)
1715{
1716 pj_status_t status;
1717
1718 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1719 port, NULL, (unsigned*)p_id);
1720 if (status != PJ_SUCCESS) {
1721 if (p_id)
1722 *p_id = PJSUA_INVALID_ID;
1723 }
1724
1725 return status;
1726}
1727
1728
1729/*
1730 * Remove arbitrary slot from the conference bridge.
1731 */
1732PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1733{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001734 pj_status_t status;
1735
1736 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1737 check_snd_dev_idle();
1738
1739 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00001740}
1741
1742
1743/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001744 * Establish unidirectional media flow from souce to sink.
1745 */
1746PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1747 pjsua_conf_port_id sink)
1748{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001749 /* If sound device idle timer is active, cancel it first. */
Benny Prijono0f711b42009-05-06 19:08:43 +00001750 PJSUA_LOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001751 if (pjsua_var.snd_idle_timer.id) {
1752 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
1753 pjsua_var.snd_idle_timer.id = PJ_FALSE;
1754 }
Benny Prijono0f711b42009-05-06 19:08:43 +00001755 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001756
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001757
Benny Prijonof798e502009-03-09 13:08:16 +00001758 /* For audio switchboard (i.e. APS-Direct):
1759 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001760 * (format, clock rate, channel count) must match to peer's.
1761 * Note that sound device can be reopened only if it doesn't have
1762 * any connection.
1763 */
Benny Prijonof798e502009-03-09 13:08:16 +00001764 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001765 pjmedia_conf_port_info port0_info;
1766 pjmedia_conf_port_info peer_info;
1767 unsigned peer_id;
1768 pj_bool_t need_reopen = PJ_FALSE;
1769 pj_status_t status;
1770
1771 peer_id = (source!=0)? source : sink;
1772 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
1773 &peer_info);
1774 pj_assert(status == PJ_SUCCESS);
1775
1776 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
1777 pj_assert(status == PJ_SUCCESS);
1778
1779 /* Check if sound device is instantiated. */
1780 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1781 !pjsua_var.no_snd);
1782
1783 /* Check if sound device need to reopen because it needs to modify
1784 * settings to match its peer. Sound device must be idle in this case
1785 * though.
1786 */
1787 if (!need_reopen &&
1788 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
1789 {
1790 need_reopen = (peer_info.format.id != port0_info.format.id ||
1791 peer_info.format.bitrate != port0_info.format.bitrate ||
1792 peer_info.clock_rate != port0_info.clock_rate ||
1793 peer_info.channel_count != port0_info.channel_count);
1794 }
1795
1796 if (need_reopen) {
Benny Prijonod65f78c2009-06-03 18:59:37 +00001797 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
1798 pjmedia_aud_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001799
Benny Prijonod65f78c2009-06-03 18:59:37 +00001800 /* Create parameter based on peer info */
1801 status = create_aud_param(&param, pjsua_var.cap_dev,
1802 pjsua_var.play_dev,
1803 peer_info.clock_rate,
1804 peer_info.channel_count,
1805 peer_info.samples_per_frame,
1806 peer_info.bits_per_sample);
1807 if (status != PJ_SUCCESS) {
1808 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1809 return status;
1810 }
Benny Prijonof798e502009-03-09 13:08:16 +00001811
Benny Prijonod65f78c2009-06-03 18:59:37 +00001812 /* And peer format */
1813 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
1814 param.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
1815 param.ext_fmt = peer_info.format;
1816 }
Benny Prijono10454dc2009-02-21 14:21:59 +00001817
Benny Prijonod65f78c2009-06-03 18:59:37 +00001818 status = open_snd_dev(&param);
1819 if (status != PJ_SUCCESS) {
1820 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1821 return status;
1822 }
1823 } else {
1824 /* Null-audio */
1825 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1826 if (status != PJ_SUCCESS) {
1827 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1828 return status;
1829 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001830 }
1831 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001832
Benny Prijonof798e502009-03-09 13:08:16 +00001833 } else {
1834 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001835
Benny Prijonof798e502009-03-09 13:08:16 +00001836 /* Create sound port if none is instantiated */
1837 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1838 !pjsua_var.no_snd)
1839 {
1840 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001841
Benny Prijonof798e502009-03-09 13:08:16 +00001842 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1843 if (status != PJ_SUCCESS) {
1844 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1845 return status;
1846 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001847 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001848
Benny Prijonof798e502009-03-09 13:08:16 +00001849 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001850
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001851 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
1852}
1853
1854
1855/*
1856 * Disconnect media flow from the source to destination port.
1857 */
1858PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
1859 pjsua_conf_port_id sink)
1860{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001861 pj_status_t status;
1862
1863 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001864 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001865
1866 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001867}
1868
1869
Benny Prijono6dd967c2006-12-26 02:27:14 +00001870/*
1871 * Adjust the signal level to be transmitted from the bridge to the
1872 * specified port by making it louder or quieter.
1873 */
1874PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
1875 float level)
1876{
1877 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
1878 (int)((level-1) * 128));
1879}
1880
1881/*
1882 * Adjust the signal level to be received from the specified port (to
1883 * the bridge) by making it louder or quieter.
1884 */
1885PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
1886 float level)
1887{
1888 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
1889 (int)((level-1) * 128));
1890}
1891
1892
1893/*
1894 * Get last signal level transmitted to or received from the specified port.
1895 */
1896PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
1897 unsigned *tx_level,
1898 unsigned *rx_level)
1899{
1900 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
1901 tx_level, rx_level);
1902}
1903
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001904/*****************************************************************************
1905 * File player.
1906 */
1907
Benny Prijonod5696da2007-07-17 16:25:45 +00001908static char* get_basename(const char *path, unsigned len)
1909{
1910 char *p = ((char*)path) + len;
1911
1912 if (len==0)
1913 return p;
1914
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001915 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00001916
1917 return (p==path) ? p : p+1;
1918}
1919
1920
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001921/*
1922 * Create a file player, and automatically connect this player to
1923 * the conference bridge.
1924 */
1925PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1926 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001927 pjsua_player_id *p_id)
1928{
1929 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001930 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00001931 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001932 pjmedia_port *port;
1933 pj_status_t status;
1934
1935 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1936 return PJ_ETOOMANY;
1937
1938 PJSUA_LOCK();
1939
1940 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1941 if (pjsua_var.player[file_id].port == NULL)
1942 break;
1943 }
1944
1945 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1946 /* This is unexpected */
1947 PJSUA_UNLOCK();
1948 pj_assert(0);
1949 return PJ_EBUG;
1950 }
1951
1952 pj_memcpy(path, filename->ptr, filename->slen);
1953 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00001954
1955 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1956 if (!pool) {
1957 PJSUA_UNLOCK();
1958 return PJ_ENOMEM;
1959 }
1960
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00001961 status = pjmedia_wav_player_port_create(
1962 pool, path,
1963 pjsua_var.mconf_cfg.samples_per_frame *
1964 1000 / pjsua_var.media_cfg.channel_count /
1965 pjsua_var.media_cfg.clock_rate,
1966 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001967 if (status != PJ_SUCCESS) {
1968 PJSUA_UNLOCK();
1969 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001970 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001971 return status;
1972 }
1973
Benny Prijono5297af92008-03-18 13:40:40 +00001974 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001975 port, filename, &slot);
1976 if (status != PJ_SUCCESS) {
1977 pjmedia_port_destroy(port);
1978 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00001979 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1980 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001981 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001982 return status;
1983 }
1984
Benny Prijonoa66c3312007-01-21 23:12:40 +00001985 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00001986 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001987 pjsua_var.player[file_id].port = port;
1988 pjsua_var.player[file_id].slot = slot;
1989
1990 if (p_id) *p_id = file_id;
1991
1992 ++pjsua_var.player_cnt;
1993
1994 PJSUA_UNLOCK();
1995 return PJ_SUCCESS;
1996}
1997
1998
1999/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00002000 * Create a file playlist media port, and automatically add the port
2001 * to the conference bridge.
2002 */
2003PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
2004 unsigned file_count,
2005 const pj_str_t *label,
2006 unsigned options,
2007 pjsua_player_id *p_id)
2008{
2009 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00002010 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002011 pjmedia_port *port;
2012 pj_status_t status;
2013
2014 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2015 return PJ_ETOOMANY;
2016
2017 PJSUA_LOCK();
2018
2019 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2020 if (pjsua_var.player[file_id].port == NULL)
2021 break;
2022 }
2023
2024 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2025 /* This is unexpected */
2026 PJSUA_UNLOCK();
2027 pj_assert(0);
2028 return PJ_EBUG;
2029 }
2030
2031
2032 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
2033 pjsua_var.media_cfg.clock_rate;
2034
Benny Prijonod5696da2007-07-17 16:25:45 +00002035 pool = pjsua_pool_create("playlist", 1000, 1000);
2036 if (!pool) {
2037 PJSUA_UNLOCK();
2038 return PJ_ENOMEM;
2039 }
2040
2041 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002042 file_names, file_count,
2043 ptime, options, 0, &port);
2044 if (status != PJ_SUCCESS) {
2045 PJSUA_UNLOCK();
2046 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002047 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002048 return status;
2049 }
2050
Benny Prijonod5696da2007-07-17 16:25:45 +00002051 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002052 port, &port->info.name, &slot);
2053 if (status != PJ_SUCCESS) {
2054 pjmedia_port_destroy(port);
2055 PJSUA_UNLOCK();
2056 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002057 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002058 return status;
2059 }
2060
2061 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00002062 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002063 pjsua_var.player[file_id].port = port;
2064 pjsua_var.player[file_id].slot = slot;
2065
2066 if (p_id) *p_id = file_id;
2067
2068 ++pjsua_var.player_cnt;
2069
2070 PJSUA_UNLOCK();
2071 return PJ_SUCCESS;
2072
2073}
2074
2075
2076/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002077 * Get conference port ID associated with player.
2078 */
2079PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
2080{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002081 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002082 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2083
2084 return pjsua_var.player[id].slot;
2085}
2086
Benny Prijono469b1522006-12-26 03:05:17 +00002087/*
2088 * Get the media port for the player.
2089 */
Benny Prijonobe41d862008-01-18 13:24:28 +00002090PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00002091 pjmedia_port **p_port)
2092{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002093 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002094 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2095 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2096
2097 *p_port = pjsua_var.player[id].port;
2098
2099 return PJ_SUCCESS;
2100}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002101
2102/*
2103 * Set playback position.
2104 */
2105PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
2106 pj_uint32_t samples)
2107{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002108 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002109 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002110 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002111
2112 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
2113}
2114
2115
2116/*
2117 * Close the file, remove the player from the bridge, and free
2118 * resources associated with the file player.
2119 */
2120PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
2121{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002122 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002123 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2124
2125 PJSUA_LOCK();
2126
2127 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002128 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002129 pjmedia_port_destroy(pjsua_var.player[id].port);
2130 pjsua_var.player[id].port = NULL;
2131 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002132 pj_pool_release(pjsua_var.player[id].pool);
2133 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002134 pjsua_var.player_cnt--;
2135 }
2136
2137 PJSUA_UNLOCK();
2138
2139 return PJ_SUCCESS;
2140}
2141
2142
2143/*****************************************************************************
2144 * File recorder.
2145 */
2146
2147/*
2148 * Create a file recorder, and automatically connect this recorder to
2149 * the conference bridge.
2150 */
2151PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00002152 unsigned enc_type,
2153 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002154 pj_ssize_t max_size,
2155 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002156 pjsua_recorder_id *p_id)
2157{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002158 enum Format
2159 {
2160 FMT_UNKNOWN,
2161 FMT_WAV,
2162 FMT_MP3,
2163 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002164 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002165 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002166 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00002167 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00002168 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002169 pjmedia_port *port;
2170 pj_status_t status;
2171
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002172 /* Filename must present */
2173 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
2174
Benny Prijono00cae612006-07-31 15:19:36 +00002175 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002176 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002177
Benny Prijono8f310522006-10-20 11:08:49 +00002178 /* Don't support encoding type at present */
2179 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002180
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002181 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
2182 return PJ_ETOOMANY;
2183
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002184 /* Determine the file format */
2185 ext.ptr = filename->ptr + filename->slen - 4;
2186 ext.slen = 4;
2187
2188 if (pj_stricmp2(&ext, ".wav") == 0)
2189 file_format = FMT_WAV;
2190 else if (pj_stricmp2(&ext, ".mp3") == 0)
2191 file_format = FMT_MP3;
2192 else {
2193 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
2194 "determine file format for %.*s",
2195 (int)filename->slen, filename->ptr));
2196 return PJ_ENOTSUP;
2197 }
2198
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002199 PJSUA_LOCK();
2200
2201 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
2202 if (pjsua_var.recorder[file_id].port == NULL)
2203 break;
2204 }
2205
2206 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
2207 /* This is unexpected */
2208 PJSUA_UNLOCK();
2209 pj_assert(0);
2210 return PJ_EBUG;
2211 }
2212
2213 pj_memcpy(path, filename->ptr, filename->slen);
2214 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002215
Benny Prijonod5696da2007-07-17 16:25:45 +00002216 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2217 if (!pool) {
2218 PJSUA_UNLOCK();
2219 return PJ_ENOMEM;
2220 }
2221
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002222 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00002223 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002224 pjsua_var.media_cfg.clock_rate,
2225 pjsua_var.mconf_cfg.channel_count,
2226 pjsua_var.mconf_cfg.samples_per_frame,
2227 pjsua_var.mconf_cfg.bits_per_sample,
2228 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002229 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00002230 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002231 port = NULL;
2232 status = PJ_ENOTSUP;
2233 }
2234
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002235 if (status != PJ_SUCCESS) {
2236 PJSUA_UNLOCK();
2237 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002238 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002239 return status;
2240 }
2241
Benny Prijonod5696da2007-07-17 16:25:45 +00002242 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002243 port, filename, &slot);
2244 if (status != PJ_SUCCESS) {
2245 pjmedia_port_destroy(port);
2246 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00002247 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002248 return status;
2249 }
2250
2251 pjsua_var.recorder[file_id].port = port;
2252 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00002253 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002254
2255 if (p_id) *p_id = file_id;
2256
2257 ++pjsua_var.rec_cnt;
2258
2259 PJSUA_UNLOCK();
2260 return PJ_SUCCESS;
2261}
2262
2263
2264/*
2265 * Get conference port associated with recorder.
2266 */
2267PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
2268{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002269 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2270 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002271 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2272
2273 return pjsua_var.recorder[id].slot;
2274}
2275
Benny Prijono469b1522006-12-26 03:05:17 +00002276/*
2277 * Get the media port for the recorder.
2278 */
2279PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
2280 pjmedia_port **p_port)
2281{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002282 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2283 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002284 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2285 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2286
2287 *p_port = pjsua_var.recorder[id].port;
2288 return PJ_SUCCESS;
2289}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002290
2291/*
2292 * Destroy recorder (this will complete recording).
2293 */
2294PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
2295{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002296 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2297 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002298 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2299
2300 PJSUA_LOCK();
2301
2302 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002303 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002304 pjmedia_port_destroy(pjsua_var.recorder[id].port);
2305 pjsua_var.recorder[id].port = NULL;
2306 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002307 pj_pool_release(pjsua_var.recorder[id].pool);
2308 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002309 pjsua_var.rec_cnt--;
2310 }
2311
2312 PJSUA_UNLOCK();
2313
2314 return PJ_SUCCESS;
2315}
2316
2317
2318/*****************************************************************************
2319 * Sound devices.
2320 */
2321
2322/*
2323 * Enum sound devices.
2324 */
Benny Prijonof798e502009-03-09 13:08:16 +00002325
2326PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002327 unsigned *count)
2328{
2329 unsigned i, dev_count;
2330
Benny Prijono10454dc2009-02-21 14:21:59 +00002331 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002332
2333 if (dev_count > *count) dev_count = *count;
2334
2335 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00002336 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002337
Benny Prijono10454dc2009-02-21 14:21:59 +00002338 status = pjmedia_aud_dev_get_info(i, &info[i]);
2339 if (status != PJ_SUCCESS)
2340 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002341 }
2342
2343 *count = dev_count;
2344
2345 return PJ_SUCCESS;
2346}
Benny Prijonof798e502009-03-09 13:08:16 +00002347
2348
Benny Prijono10454dc2009-02-21 14:21:59 +00002349PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
2350 unsigned *count)
2351{
2352 unsigned i, dev_count;
2353
2354 dev_count = pjmedia_aud_dev_count();
2355
2356 if (dev_count > *count) dev_count = *count;
2357 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
2358
2359 for (i=0; i<dev_count; ++i) {
2360 pjmedia_aud_dev_info ai;
2361 pj_status_t status;
2362
2363 status = pjmedia_aud_dev_get_info(i, &ai);
2364 if (status != PJ_SUCCESS)
2365 return status;
2366
2367 strncpy(info[i].name, ai.name, sizeof(info[i].name));
2368 info[i].name[sizeof(info[i].name)-1] = '\0';
2369 info[i].input_count = ai.input_count;
2370 info[i].output_count = ai.output_count;
2371 info[i].default_samples_per_sec = ai.default_samples_per_sec;
2372 }
2373
2374 *count = dev_count;
2375
2376 return PJ_SUCCESS;
2377}
Benny Prijono10454dc2009-02-21 14:21:59 +00002378
Benny Prijonof798e502009-03-09 13:08:16 +00002379/* Create audio device parameter to open the device */
2380static pj_status_t create_aud_param(pjmedia_aud_param *param,
2381 pjmedia_aud_dev_index capture_dev,
2382 pjmedia_aud_dev_index playback_dev,
2383 unsigned clock_rate,
2384 unsigned channel_count,
2385 unsigned samples_per_frame,
2386 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002387{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002388 pj_status_t status;
2389
Benny Prijono96e74f32009-02-22 12:00:12 +00002390 /* Normalize device ID with new convention about default device ID */
2391 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
2392 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
2393
Benny Prijono10454dc2009-02-21 14:21:59 +00002394 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00002395 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00002396 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00002397 pjsua_perror(THIS_FILE, "Error retrieving default audio "
2398 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00002399 return status;
2400 }
Benny Prijonof798e502009-03-09 13:08:16 +00002401 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
2402 param->rec_id = capture_dev;
2403 param->play_id = playback_dev;
2404 param->clock_rate = clock_rate;
2405 param->channel_count = channel_count;
2406 param->samples_per_frame = samples_per_frame;
2407 param->bits_per_sample = bits_per_sample;
2408
2409 /* Update the setting with user preference */
2410#define update_param(cap, field) \
2411 if (pjsua_var.aud_param.flags & cap) { \
2412 param->flags |= cap; \
2413 param->field = pjsua_var.aud_param.field; \
2414 }
2415 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
2416 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
2417 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
2418 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
2419#undef update_param
2420
Benny Prijono10454dc2009-02-21 14:21:59 +00002421 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00002422 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
2423 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
2424 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
2425 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
2426
Benny Prijono10454dc2009-02-21 14:21:59 +00002427 /* EC settings */
2428 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00002429 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
2430 param->ec_enabled = PJ_TRUE;
2431 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00002432 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00002433 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00002434 }
2435
Benny Prijonof798e502009-03-09 13:08:16 +00002436 return PJ_SUCCESS;
2437}
Benny Prijono26056d82006-10-11 16:03:41 +00002438
Benny Prijonof798e502009-03-09 13:08:16 +00002439/* Internal: the first time the audio device is opened (during app
2440 * startup), retrieve the audio settings such as volume level
2441 * so that aud_get_settings() will work.
2442 */
2443static pj_status_t update_initial_aud_param()
2444{
2445 pjmedia_aud_stream *strm;
2446 pjmedia_aud_param param;
2447 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00002448
Benny Prijonof798e502009-03-09 13:08:16 +00002449 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002450
Benny Prijonof798e502009-03-09 13:08:16 +00002451 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00002452
Benny Prijonof798e502009-03-09 13:08:16 +00002453 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002454 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00002455 pjsua_perror(THIS_FILE, "Error audio stream "
2456 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002457 return status;
2458 }
2459
Benny Prijonof798e502009-03-09 13:08:16 +00002460#define update_saved_param(cap, field) \
2461 if (param.flags & cap) { \
2462 pjsua_var.aud_param.flags |= cap; \
2463 pjsua_var.aud_param.field = param.field; \
2464 }
2465
2466 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
2467 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
2468 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
2469 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
2470#undef update_saved_param
2471
2472 return PJ_SUCCESS;
2473}
2474
2475/* Get format name */
2476static const char *get_fmt_name(pj_uint32_t id)
2477{
2478 static char name[8];
2479
2480 if (id == PJMEDIA_FORMAT_L16)
2481 return "PCM";
2482 pj_memcpy(name, &id, 4);
2483 name[4] = '\0';
2484 return name;
2485}
2486
2487/* Open sound device with the setting. */
2488static pj_status_t open_snd_dev(pjmedia_aud_param *param)
2489{
2490 pjmedia_port *conf_port;
2491 pj_status_t status;
2492
2493 PJ_ASSERT_RETURN(param, PJ_EINVAL);
2494
2495 /* Check if NULL sound device is used */
2496 if (NULL_SND_DEV_ID==param->rec_id || NULL_SND_DEV_ID==param->play_id) {
2497 return pjsua_set_null_snd_dev();
2498 }
2499
2500 /* Close existing sound port */
2501 close_snd_dev();
2502
2503 /* Create memory pool for sound device. */
2504 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2505 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2506
2507
2508 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
2509 get_fmt_name(param->ext_fmt.id),
2510 param->clock_rate, param->channel_count,
2511 param->samples_per_frame / param->channel_count * 1000 /
2512 param->clock_rate));
2513
2514 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
2515 param, &pjsua_var.snd_port);
2516 if (status != PJ_SUCCESS)
2517 return status;
2518
2519 /* Get the port0 of the conference bridge. */
2520 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2521 pj_assert(conf_port != NULL);
2522
2523 /* For conference bridge, resample if necessary if the bridge's
2524 * clock rate is different than the sound device's clock rate.
2525 */
2526 if (!pjsua_var.is_mswitch &&
2527 param->ext_fmt.id == PJMEDIA_FORMAT_PCM &&
2528 conf_port->info.clock_rate != param->clock_rate)
2529 {
2530 pjmedia_port *resample_port;
2531 unsigned resample_opt = 0;
2532
2533 if (pjsua_var.media_cfg.quality >= 3 &&
2534 pjsua_var.media_cfg.quality <= 4)
2535 {
2536 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
2537 }
2538 else if (pjsua_var.media_cfg.quality < 3) {
2539 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
2540 }
2541
2542 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
2543 conf_port,
2544 param->clock_rate,
2545 resample_opt,
2546 &resample_port);
2547 if (status != PJ_SUCCESS) {
2548 char errmsg[PJ_ERR_MSG_SIZE];
2549 pj_strerror(status, errmsg, sizeof(errmsg));
2550 PJ_LOG(4, (THIS_FILE,
2551 "Error creating resample port: %s",
2552 errmsg));
2553 close_snd_dev();
2554 return status;
2555 }
2556
2557 conf_port = resample_port;
2558 }
2559
2560 /* Otherwise for audio switchboard, the switch's port0 setting is
2561 * derived from the sound device setting, so update the setting.
2562 */
2563 if (pjsua_var.is_mswitch) {
2564 pj_memcpy(&conf_port->info.format, &param->ext_fmt,
2565 sizeof(conf_port->info.format));
2566 conf_port->info.clock_rate = param->clock_rate;
2567 conf_port->info.samples_per_frame = param->samples_per_frame;
2568 conf_port->info.channel_count = param->channel_count;
2569 conf_port->info.bits_per_sample = 16;
2570 }
2571
2572 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00002573 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
2574 conf_port );
2575 if (status != PJ_SUCCESS) {
2576 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
2577 "sound device", status);
2578 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2579 pjsua_var.snd_port = NULL;
2580 return status;
2581 }
2582
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002583 /* Save the device IDs */
Benny Prijonof798e502009-03-09 13:08:16 +00002584 pjsua_var.cap_dev = param->rec_id;
2585 pjsua_var.play_dev = param->play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002586
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002587 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00002588 {
2589 pjmedia_aud_dev_info rec_info;
2590 pjmedia_aud_stream *strm;
2591 pjmedia_aud_param si;
2592 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002593
Benny Prijonof798e502009-03-09 13:08:16 +00002594 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2595 status = pjmedia_aud_stream_get_param(strm, &si);
2596 if (status == PJ_SUCCESS)
2597 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00002598
Benny Prijonof798e502009-03-09 13:08:16 +00002599 if (status==PJ_SUCCESS) {
2600 if (param->clock_rate != pjsua_var.media_cfg.clock_rate) {
2601 char tmp_buf[128];
2602 int tmp_buf_len = sizeof(tmp_buf);
2603
2604 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
2605 "%s (%dKHz)",
2606 rec_info.name,
2607 param->clock_rate/1000);
2608 pj_strset(&tmp, tmp_buf, tmp_buf_len);
2609 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
2610 } else {
2611 pjmedia_conf_set_port0_name(pjsua_var.mconf,
2612 pj_cstr(&tmp, rec_info.name));
2613 }
2614 }
2615
2616 /* Any error is not major, let it through */
2617 status = PJ_SUCCESS;
2618 };
2619
2620 /* If this is the first time the audio device is open, retrieve some
2621 * settings from the device (such as volume settings) so that the
2622 * pjsua_snd_get_setting() work.
2623 */
2624 if (pjsua_var.aud_open_cnt == 0) {
2625 update_initial_aud_param();
2626 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00002627 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002628
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002629 return PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00002630}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002631
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002632
Benny Prijonof798e502009-03-09 13:08:16 +00002633/* Close existing sound device */
2634static void close_snd_dev(void)
2635{
2636 /* Close sound device */
2637 if (pjsua_var.snd_port) {
2638 pjmedia_aud_dev_info cap_info, play_info;
2639 pjmedia_aud_stream *strm;
2640 pjmedia_aud_param param;
2641
2642 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2643 pjmedia_aud_stream_get_param(strm, &param);
2644
2645 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
2646 cap_info.name[0] = '\0';
2647 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
2648 play_info.name[0] = '\0';
2649
2650 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
2651 "%s sound capture device",
2652 play_info.name, cap_info.name));
2653
2654 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
2655 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2656 pjsua_var.snd_port = NULL;
2657 }
2658
2659 /* Close null sound device */
2660 if (pjsua_var.null_snd) {
2661 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
2662 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
2663 pjsua_var.null_snd = NULL;
2664 }
2665
2666 if (pjsua_var.snd_pool)
2667 pj_pool_release(pjsua_var.snd_pool);
2668 pjsua_var.snd_pool = NULL;
2669}
2670
2671
2672/*
2673 * Select or change sound device. Application may call this function at
2674 * any time to replace current sound device.
2675 */
2676PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
2677 int playback_dev)
2678{
2679 unsigned alt_cr_cnt = 1;
2680 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
2681 unsigned i;
2682 pj_status_t status = -1;
2683
Benny Prijono23ea21a2009-06-03 12:43:06 +00002684 /* Null-sound */
2685 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
2686 return pjsua_set_null_snd_dev();
2687 }
2688
Benny Prijonof798e502009-03-09 13:08:16 +00002689 /* Set default clock rate */
2690 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
2691 if (alt_cr[0] == 0)
2692 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
2693
2694 /* Allow retrying of different clock rate if we're using conference
2695 * bridge (meaning audio format is always PCM), otherwise lock on
2696 * to one clock rate.
2697 */
2698 if (pjsua_var.is_mswitch) {
2699 alt_cr_cnt = 1;
2700 } else {
2701 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
2702 }
2703
2704 /* Attempts to open the sound device with different clock rates */
2705 for (i=0; i<alt_cr_cnt; ++i) {
2706 pjmedia_aud_param param;
2707 unsigned samples_per_frame;
2708
2709 /* Create the default audio param */
2710 samples_per_frame = alt_cr[i] *
2711 pjsua_var.media_cfg.audio_frame_ptime *
2712 pjsua_var.media_cfg.channel_count / 1000;
2713 status = create_aud_param(&param, capture_dev, playback_dev,
2714 alt_cr[i], pjsua_var.media_cfg.channel_count,
2715 samples_per_frame, 16);
2716 if (status != PJ_SUCCESS)
2717 return status;
2718
2719 /* Open! */
2720 status = open_snd_dev(&param);
2721 if (status == PJ_SUCCESS)
2722 break;
2723 }
2724
2725 if (status != PJ_SUCCESS) {
2726 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
2727 return status;
2728 }
2729
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00002730 pjsua_var.no_snd = PJ_FALSE;
2731
Benny Prijonof798e502009-03-09 13:08:16 +00002732 return PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002733}
2734
2735
2736/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00002737 * Get currently active sound devices. If sound devices has not been created
2738 * (for example when pjsua_start() is not called), it is possible that
2739 * the function returns PJ_SUCCESS with -1 as device IDs.
2740 */
2741PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
2742 int *playback_dev)
2743{
2744 if (capture_dev) {
2745 *capture_dev = pjsua_var.cap_dev;
2746 }
2747 if (playback_dev) {
2748 *playback_dev = pjsua_var.play_dev;
2749 }
2750
2751 return PJ_SUCCESS;
2752}
2753
2754
2755/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002756 * Use null sound device.
2757 */
2758PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
2759{
2760 pjmedia_port *conf_port;
2761 pj_status_t status;
2762
2763 /* Close existing sound device */
2764 close_snd_dev();
2765
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002766 /* Create memory pool for sound device. */
2767 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2768 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2769
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002770 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
2771
2772 /* Get the port0 of the conference bridge. */
2773 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2774 pj_assert(conf_port != NULL);
2775
2776 /* Create master port, connecting port0 of the conference bridge to
2777 * a null port.
2778 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002779 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002780 conf_port, 0, &pjsua_var.null_snd);
2781 if (status != PJ_SUCCESS) {
2782 pjsua_perror(THIS_FILE, "Unable to create null sound device",
2783 status);
2784 return status;
2785 }
2786
2787 /* Start the master port */
2788 status = pjmedia_master_port_start(pjsua_var.null_snd);
2789 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2790
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002791 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2792 pjsua_var.play_dev = NULL_SND_DEV_ID;
2793
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00002794 pjsua_var.no_snd = PJ_FALSE;
2795
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002796 return PJ_SUCCESS;
2797}
2798
2799
Benny Prijonoe909eac2006-07-27 22:04:56 +00002800
2801/*
2802 * Use no device!
2803 */
2804PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2805{
2806 /* Close existing sound device */
2807 close_snd_dev();
2808
2809 pjsua_var.no_snd = PJ_TRUE;
2810 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2811}
2812
2813
Benny Prijonof20687a2006-08-04 18:27:19 +00002814/*
2815 * Configure the AEC settings of the sound port.
2816 */
Benny Prijono5da50432006-08-07 10:24:52 +00002817PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00002818{
2819 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2820
2821 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00002822 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
2823 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00002824
2825 return PJ_SUCCESS;
2826}
2827
2828
2829/*
2830 * Get current AEC tail length.
2831 */
Benny Prijono22dfe592006-08-06 12:07:13 +00002832PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00002833{
2834 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2835 return PJ_SUCCESS;
2836}
2837
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002838
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002839/*
Benny Prijonof798e502009-03-09 13:08:16 +00002840 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002841 */
Benny Prijonof798e502009-03-09 13:08:16 +00002842PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002843{
Benny Prijonof798e502009-03-09 13:08:16 +00002844 return pjsua_var.snd_port != NULL;
2845}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002846
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002847
Benny Prijonof798e502009-03-09 13:08:16 +00002848/*
2849 * Configure sound device setting to the sound device being used.
2850 */
2851PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
2852 const void *pval,
2853 pj_bool_t keep)
2854{
Benny Prijono09b0ff62009-03-10 12:07:51 +00002855 pj_status_t status;
2856
Benny Prijonof798e502009-03-09 13:08:16 +00002857 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00002858 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00002859 return PJMEDIA_EAUD_INVCAP;
2860 }
2861
Benny Prijono09b0ff62009-03-10 12:07:51 +00002862 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00002863 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00002864 pjmedia_aud_stream *strm;
2865
2866 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00002867 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00002868 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00002869 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00002870 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00002871
2872 if (status != PJ_SUCCESS)
2873 return status;
2874
2875 /* Save in internal param for later device open */
2876 if (keep) {
2877 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
2878 cap, pval);
2879 }
2880
2881 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00002882}
2883
2884/*
2885 * Retrieve a sound device setting.
2886 */
2887PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
2888 void *pval)
2889{
2890 /* If sound device has never been opened before, open it to
2891 * retrieve the initial setting from the device (e.g. audio
2892 * volume)
2893 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00002894 if (pjsua_var.aud_open_cnt==0) {
2895 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00002896 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00002897 close_snd_dev();
2898 }
Benny Prijonof798e502009-03-09 13:08:16 +00002899
2900 if (pjsua_snd_is_active()) {
2901 /* Sound is active, retrieve from device directly */
2902 pjmedia_aud_stream *strm;
2903
2904 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2905 return pjmedia_aud_stream_get_cap(strm, cap, pval);
2906 } else {
2907 /* Otherwise retrieve from internal param */
2908 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
2909 cap, pval);
2910 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002911}
2912
2913
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002914/*****************************************************************************
2915 * Codecs.
2916 */
2917
2918/*
2919 * Enum all supported codecs in the system.
2920 */
2921PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
2922 unsigned *p_count )
2923{
2924 pjmedia_codec_mgr *codec_mgr;
2925 pjmedia_codec_info info[32];
2926 unsigned i, count, prio[32];
2927 pj_status_t status;
2928
2929 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2930 count = PJ_ARRAY_SIZE(info);
2931 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
2932 if (status != PJ_SUCCESS) {
2933 *p_count = 0;
2934 return status;
2935 }
2936
2937 if (count > *p_count) count = *p_count;
2938
2939 for (i=0; i<count; ++i) {
2940 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
2941 id[i].codec_id = pj_str(id[i].buf_);
2942 id[i].priority = (pj_uint8_t) prio[i];
2943 }
2944
2945 *p_count = count;
2946
2947 return PJ_SUCCESS;
2948}
2949
2950
2951/*
2952 * Change codec priority.
2953 */
2954PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
2955 pj_uint8_t priority )
2956{
Benny Prijono88accae2008-06-26 15:48:14 +00002957 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002958 pjmedia_codec_mgr *codec_mgr;
2959
2960 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2961
Benny Prijono88accae2008-06-26 15:48:14 +00002962 if (codec_id->slen==1 && *codec_id->ptr=='*')
2963 codec_id = &all;
2964
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002965 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
2966 priority);
2967}
2968
2969
2970/*
2971 * Get codec parameters.
2972 */
2973PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
2974 pjmedia_codec_param *param )
2975{
Benny Prijono88accae2008-06-26 15:48:14 +00002976 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002977 const pjmedia_codec_info *info;
2978 pjmedia_codec_mgr *codec_mgr;
2979 unsigned count = 1;
2980 pj_status_t status;
2981
2982 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2983
Benny Prijono88accae2008-06-26 15:48:14 +00002984 if (codec_id->slen==1 && *codec_id->ptr=='*')
2985 codec_id = &all;
2986
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002987 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
2988 &count, &info, NULL);
2989 if (status != PJ_SUCCESS)
2990 return status;
2991
2992 if (count != 1)
2993 return PJ_ENOTFOUND;
2994
2995 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
2996 return status;
2997}
2998
2999
3000/*
3001 * Set codec parameters.
3002 */
3003PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
3004 const pjmedia_codec_param *param)
3005{
Benny Prijono00cae612006-07-31 15:19:36 +00003006 PJ_UNUSED_ARG(id);
3007 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003008 PJ_TODO(set_codec_param);
3009 return PJ_SUCCESS;
3010}