blob: 036f72041bf30893dc80cba17a78be136d7ad804 [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
38/* Close existing sound device */
39static void close_snd_dev(void);
40
41
Benny Prijonof76e1392008-06-06 14:51:48 +000042static void pjsua_media_config_dup(pj_pool_t *pool,
43 pjsua_media_config *dst,
44 const pjsua_media_config *src)
45{
46 pj_memcpy(dst, src, sizeof(*src));
47 pj_strdup(pool, &dst->turn_server, &src->turn_server);
48 pj_stun_auth_cred_dup(pool, &dst->turn_auth_cred, &src->turn_auth_cred);
49}
50
Benny Prijonoeebe9af2006-06-13 22:57:13 +000051/**
52 * Init media subsystems.
53 */
54pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
55{
Benny Prijonoba5926a2007-05-02 11:29:37 +000056 pj_str_t codec_id = {NULL, 0};
Benny Prijono0498d902006-06-19 14:49:14 +000057 unsigned opt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000058 pj_status_t status;
59
Benny Prijonofc24e692007-01-27 18:31:51 +000060 /* To suppress warning about unused var when all codecs are disabled */
61 PJ_UNUSED_ARG(codec_id);
62
Benny Prijonoeebe9af2006-06-13 22:57:13 +000063 /* Copy configuration */
Benny Prijonof76e1392008-06-06 14:51:48 +000064 pjsua_media_config_dup(pjsua_var.pool, &pjsua_var.media_cfg, cfg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000065
66 /* Normalize configuration */
Benny Prijono50f19b32008-03-11 13:15:43 +000067 if (pjsua_var.media_cfg.snd_clock_rate == 0) {
68 pjsua_var.media_cfg.snd_clock_rate = pjsua_var.media_cfg.clock_rate;
69 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +000070
71 if (pjsua_var.media_cfg.has_ioqueue &&
72 pjsua_var.media_cfg.thread_cnt == 0)
73 {
74 pjsua_var.media_cfg.thread_cnt = 1;
75 }
76
77 if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
78 pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
79 }
80
81 /* Create media endpoint. */
82 status = pjmedia_endpt_create(&pjsua_var.cp.factory,
83 pjsua_var.media_cfg.has_ioqueue? NULL :
84 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
85 pjsua_var.media_cfg.thread_cnt,
86 &pjsua_var.med_endpt);
87 if (status != PJ_SUCCESS) {
88 pjsua_perror(THIS_FILE,
89 "Media stack initialization has returned error",
90 status);
91 return status;
92 }
93
94 /* Register all codecs */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +000095
Benny Prijonoeebe9af2006-06-13 22:57:13 +000096#if PJMEDIA_HAS_SPEEX_CODEC
97 /* Register speex. */
Nanang Izzuddin9dbad152008-06-10 18:56:10 +000098 status = pjmedia_codec_speex_init(pjsua_var.med_endpt,
99 0,
100 pjsua_var.media_cfg.quality,
101 -1);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000102 if (status != PJ_SUCCESS) {
103 pjsua_perror(THIS_FILE, "Error initializing Speex codec",
104 status);
105 return status;
106 }
Benny Prijono7ca96da2006-08-07 12:11:40 +0000107
108 /* Set speex/16000 to higher priority*/
109 codec_id = pj_str("speex/16000");
110 pjmedia_codec_mgr_set_codec_priority(
111 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
112 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
113
114 /* Set speex/8000 to next higher priority*/
115 codec_id = pj_str("speex/8000");
116 pjmedia_codec_mgr_set_codec_priority(
117 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
118 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
119
120
121
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000122#endif /* PJMEDIA_HAS_SPEEX_CODEC */
123
Benny Prijono00cae612006-07-31 15:19:36 +0000124#if PJMEDIA_HAS_ILBC_CODEC
125 /* Register iLBC. */
126 status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt,
127 pjsua_var.media_cfg.ilbc_mode);
128 if (status != PJ_SUCCESS) {
129 pjsua_perror(THIS_FILE, "Error initializing iLBC codec",
130 status);
131 return status;
132 }
133#endif /* PJMEDIA_HAS_ILBC_CODEC */
134
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000135#if PJMEDIA_HAS_GSM_CODEC
136 /* Register GSM */
137 status = pjmedia_codec_gsm_init(pjsua_var.med_endpt);
138 if (status != PJ_SUCCESS) {
139 pjsua_perror(THIS_FILE, "Error initializing GSM codec",
140 status);
141 return status;
142 }
143#endif /* PJMEDIA_HAS_GSM_CODEC */
144
145#if PJMEDIA_HAS_G711_CODEC
146 /* Register PCMA and PCMU */
147 status = pjmedia_codec_g711_init(pjsua_var.med_endpt);
148 if (status != PJ_SUCCESS) {
149 pjsua_perror(THIS_FILE, "Error initializing G711 codec",
150 status);
151 return status;
152 }
153#endif /* PJMEDIA_HAS_G711_CODEC */
154
Benny Prijono7ffd7752008-03-17 14:07:53 +0000155#if PJMEDIA_HAS_G722_CODEC
156 status = pjmedia_codec_g722_init( pjsua_var.med_endpt );
157 if (status != PJ_SUCCESS) {
158 pjsua_perror(THIS_FILE, "Error initializing G722 codec",
159 status);
160 return status;
161 }
162#endif /* PJMEDIA_HAS_G722_CODEC */
163
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000164#if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000165 /* Register IPP codecs */
166 status = pjmedia_codec_ipp_init(pjsua_var.med_endpt);
167 if (status != PJ_SUCCESS) {
168 pjsua_perror(THIS_FILE, "Error initializing IPP codecs",
169 status);
170 return status;
171 }
172
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000173#endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000174
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000175#if PJMEDIA_HAS_PASSTHROUGH_CODECS
176 /* Register passthrough codecs */
177 status = pjmedia_codec_passthrough_init(pjsua_var.med_endpt);
178 if (status != PJ_SUCCESS) {
179 pjsua_perror(THIS_FILE, "Error initializing passthrough codecs",
180 status);
181 return status;
182 }
183#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
184
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000185#if PJMEDIA_HAS_L16_CODEC
186 /* Register L16 family codecs, but disable all */
187 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
188 if (status != PJ_SUCCESS) {
189 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
190 status);
191 return status;
192 }
193
194 /* Disable ALL L16 codecs */
195 codec_id = pj_str("L16");
196 pjmedia_codec_mgr_set_codec_priority(
197 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
198 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
199
200#endif /* PJMEDIA_HAS_L16_CODEC */
201
202
203 /* Save additional conference bridge parameters for future
204 * reference.
205 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000206 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000207 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000208 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
209 pjsua_var.mconf_cfg.channel_count *
210 pjsua_var.media_cfg.audio_frame_ptime /
211 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000212
Benny Prijono0498d902006-06-19 14:49:14 +0000213 /* Init options for conference bridge. */
214 opt = PJMEDIA_CONF_NO_DEVICE;
215 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000216 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000217 {
218 opt |= PJMEDIA_CONF_SMALL_FILTER;
219 }
220 else if (pjsua_var.media_cfg.quality < 3) {
221 opt |= PJMEDIA_CONF_USE_LINEAR;
222 }
223
224
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000225 /* Init conference bridge. */
226 status = pjmedia_conf_create(pjsua_var.pool,
227 pjsua_var.media_cfg.max_media_ports,
228 pjsua_var.media_cfg.clock_rate,
229 pjsua_var.mconf_cfg.channel_count,
230 pjsua_var.mconf_cfg.samples_per_frame,
231 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000232 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000233 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000234 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000235 status);
236 return status;
237 }
238
239 /* Create null port just in case user wants to use null sound. */
240 status = pjmedia_null_port_create(pjsua_var.pool,
241 pjsua_var.media_cfg.clock_rate,
242 pjsua_var.mconf_cfg.channel_count,
243 pjsua_var.mconf_cfg.samples_per_frame,
244 pjsua_var.mconf_cfg.bits_per_sample,
245 &pjsua_var.null_port);
246 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
247
Benny Prijono6ba8c542007-10-16 01:34:14 +0000248 /* Perform NAT detection */
249 pjsua_detect_nat_type();
250
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000251 return PJ_SUCCESS;
252}
253
254
255/*
256 * Create RTP and RTCP socket pair, and possibly resolve their public
257 * address via STUN.
258 */
259static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
260 pjmedia_sock_info *skinfo)
261{
262 enum {
263 RTP_RETRY = 100
264 };
265 int i;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000266 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000267 pj_sockaddr_in mapped_addr[2];
268 pj_status_t status = PJ_SUCCESS;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000269 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000270 pj_sock_t sock[2];
271
Benny Prijonoc97608e2007-03-23 16:34:20 +0000272 /* Make sure STUN server resolution has completed */
273 status = pjsua_resolve_stun_server(PJ_TRUE);
274 if (status != PJ_SUCCESS) {
275 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
276 return status;
277 }
278
Benny Prijonode479562007-03-15 10:23:55 +0000279 if (next_rtp_port == 0)
280 next_rtp_port = (pj_uint16_t)cfg->port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000281
282 for (i=0; i<2; ++i)
283 sock[i] = PJ_INVALID_SOCKET;
284
Benny Prijono0a5cad82006-09-26 13:21:02 +0000285 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
286 if (cfg->bound_addr.slen) {
287 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
288 if (status != PJ_SUCCESS) {
289 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
290 status);
291 return status;
292 }
293 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000294
295 /* Loop retry to bind RTP and RTCP sockets. */
Benny Prijonode479562007-03-15 10:23:55 +0000296 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000297
298 /* Create and bind RTP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000299 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000300 if (status != PJ_SUCCESS) {
301 pjsua_perror(THIS_FILE, "socket() error", status);
302 return status;
303 }
304
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000305 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
306 next_rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000307 if (status != PJ_SUCCESS) {
308 pj_sock_close(sock[0]);
309 sock[0] = PJ_INVALID_SOCKET;
310 continue;
311 }
312
313 /* Create and bind RTCP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000314 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000315 if (status != PJ_SUCCESS) {
316 pjsua_perror(THIS_FILE, "socket() error", status);
317 pj_sock_close(sock[0]);
318 return status;
319 }
320
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000321 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
322 (pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000323 if (status != PJ_SUCCESS) {
324 pj_sock_close(sock[0]);
325 sock[0] = PJ_INVALID_SOCKET;
326
327 pj_sock_close(sock[1]);
328 sock[1] = PJ_INVALID_SOCKET;
329 continue;
330 }
331
332 /*
333 * If we're configured to use STUN, then find out the mapped address,
334 * and make sure that the mapped RTCP port is adjacent with the RTP.
335 */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000336 if (pjsua_var.stun_srv.addr.sa_family != 0) {
337 char ip_addr[32];
338 pj_str_t stun_srv;
339
340 pj_ansi_strcpy(ip_addr,
341 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
342 stun_srv = pj_str(ip_addr);
343
Benny Prijono14c2b862007-02-21 00:40:05 +0000344 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000345 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
346 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000347 mapped_addr);
348 if (status != PJ_SUCCESS) {
349 pjsua_perror(THIS_FILE, "STUN resolve error", status);
350 goto on_error;
351 }
352
Benny Prijono80eee892007-11-03 22:43:23 +0000353#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000354 if (pj_ntohs(mapped_addr[1].sin_port) ==
355 pj_ntohs(mapped_addr[0].sin_port)+1)
356 {
357 /* Success! */
358 break;
359 }
360
361 pj_sock_close(sock[0]);
362 sock[0] = PJ_INVALID_SOCKET;
363
364 pj_sock_close(sock[1]);
365 sock[1] = PJ_INVALID_SOCKET;
Benny Prijono80eee892007-11-03 22:43:23 +0000366#else
367 if (pj_ntohs(mapped_addr[1].sin_port) !=
368 pj_ntohs(mapped_addr[0].sin_port)+1)
369 {
370 PJ_LOG(4,(THIS_FILE,
371 "Note: STUN mapped RTCP port %d is not adjacent"
372 " to RTP port %d",
373 pj_ntohs(mapped_addr[1].sin_port),
374 pj_ntohs(mapped_addr[0].sin_port)));
375 }
376 /* Success! */
377 break;
378#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000379
Benny Prijono0a5cad82006-09-26 13:21:02 +0000380 } else if (cfg->public_addr.slen) {
381
382 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000383 (pj_uint16_t)next_rtp_port);
Benny Prijono0a5cad82006-09-26 13:21:02 +0000384 if (status != PJ_SUCCESS)
385 goto on_error;
386
387 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000388 (pj_uint16_t)(next_rtp_port+1));
Benny Prijono0a5cad82006-09-26 13:21:02 +0000389 if (status != PJ_SUCCESS)
390 goto on_error;
391
392 break;
393
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000394 } else {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000395
Benny Prijono42d08d22007-12-20 11:23:07 +0000396 if (bound_addr.sin_addr.s_addr == 0) {
397 pj_sockaddr addr;
398
399 /* Get local IP address. */
400 status = pj_gethostip(pj_AF_INET(), &addr);
401 if (status != PJ_SUCCESS)
402 goto on_error;
403
404 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
405 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000406
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000407 for (i=0; i<2; ++i) {
408 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
Benny Prijono42d08d22007-12-20 11:23:07 +0000409 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000410 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000411
Benny Prijonode479562007-03-15 10:23:55 +0000412 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
413 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000414 break;
415 }
416 }
417
418 if (sock[0] == PJ_INVALID_SOCKET) {
419 PJ_LOG(1,(THIS_FILE,
420 "Unable to find appropriate RTP/RTCP ports combination"));
421 goto on_error;
422 }
423
424
425 skinfo->rtp_sock = sock[0];
426 pj_memcpy(&skinfo->rtp_addr_name,
427 &mapped_addr[0], sizeof(pj_sockaddr_in));
428
429 skinfo->rtcp_sock = sock[1];
430 pj_memcpy(&skinfo->rtcp_addr_name,
431 &mapped_addr[1], sizeof(pj_sockaddr_in));
432
Benny Prijono8b22ce12008-02-08 12:57:55 +0000433 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000434 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
435 sizeof(addr_buf), 3)));
Benny Prijono8b22ce12008-02-08 12:57:55 +0000436 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000437 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
438 sizeof(addr_buf), 3)));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000439
Benny Prijonode479562007-03-15 10:23:55 +0000440 next_rtp_port += 2;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000441 return PJ_SUCCESS;
442
443on_error:
444 for (i=0; i<2; ++i) {
445 if (sock[i] != PJ_INVALID_SOCKET)
446 pj_sock_close(sock[i]);
447 }
448 return status;
449}
450
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000451/* Check if sound device is idle. */
452static void check_snd_dev_idle()
453{
454
455 /* Activate sound device auto-close timer if sound device is idle.
456 * It is idle when there is no port connection in the bridge.
457 */
458 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) &&
459 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
460 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
461 pjsua_var.media_cfg.snd_auto_close_time >= 0)
462 {
463 pj_time_val delay;
464
465 delay.msec = 0;
466 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
467
468 pjsua_var.snd_idle_timer.id = PJ_TRUE;
469 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
470 &delay);
471 }
472}
473
474
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000475/* Timer callback to close sound device */
476static void close_snd_timer_cb( pj_timer_heap_t *th,
477 pj_timer_entry *entry)
478{
479 PJ_UNUSED_ARG(th);
480
481 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
482 pjsua_var.media_cfg.snd_auto_close_time));
483
484 entry->id = PJ_FALSE;
485
486 close_snd_dev();
487}
488
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000489
490/*
491 * Start pjsua media subsystem.
492 */
493pj_status_t pjsua_media_subsys_start(void)
494{
495 pj_status_t status;
496
497 /* Create media for calls, if none is specified */
498 if (pjsua_var.calls[0].med_tp == NULL) {
499 pjsua_transport_config transport_cfg;
500
501 /* Create default transport config */
502 pjsua_transport_config_default(&transport_cfg);
503 transport_cfg.port = DEFAULT_RTP_PORT;
504
505 status = pjsua_media_transports_create(&transport_cfg);
506 if (status != PJ_SUCCESS)
507 return status;
508 }
509
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000510 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
511 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000512
513 return PJ_SUCCESS;
514}
515
516
517/*
518 * Destroy pjsua media subsystem.
519 */
520pj_status_t pjsua_media_subsys_destroy(void)
521{
522 unsigned i;
523
524 close_snd_dev();
525
526 if (pjsua_var.mconf) {
527 pjmedia_conf_destroy(pjsua_var.mconf);
528 pjsua_var.mconf = NULL;
529 }
530
531 if (pjsua_var.null_port) {
532 pjmedia_port_destroy(pjsua_var.null_port);
533 pjsua_var.null_port = NULL;
534 }
535
536 /* Destroy file players */
537 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
538 if (pjsua_var.player[i].port) {
539 pjmedia_port_destroy(pjsua_var.player[i].port);
540 pjsua_var.player[i].port = NULL;
541 }
542 }
543
544 /* Destroy file recorders */
545 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
546 if (pjsua_var.recorder[i].port) {
547 pjmedia_port_destroy(pjsua_var.recorder[i].port);
548 pjsua_var.recorder[i].port = NULL;
549 }
550 }
551
552 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000553 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono311b63f2008-07-14 11:31:40 +0000554 if (pjsua_var.calls[i].med_tp_st != PJSUA_MED_TP_IDLE) {
555 pjsua_media_channel_deinit(i);
556 }
Benny Prijono40860c32008-09-04 13:55:33 +0000557 if (pjsua_var.calls[i].med_tp && pjsua_var.calls[i].med_tp_auto_del) {
558 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000559 }
Benny Prijono40860c32008-09-04 13:55:33 +0000560 pjsua_var.calls[i].med_tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000561 }
562
563 /* Destroy media endpoint. */
564 if (pjsua_var.med_endpt) {
565
566 /* Shutdown all codecs: */
567# if PJMEDIA_HAS_SPEEX_CODEC
568 pjmedia_codec_speex_deinit();
569# endif /* PJMEDIA_HAS_SPEEX_CODEC */
570
571# if PJMEDIA_HAS_GSM_CODEC
572 pjmedia_codec_gsm_deinit();
573# endif /* PJMEDIA_HAS_GSM_CODEC */
574
575# if PJMEDIA_HAS_G711_CODEC
576 pjmedia_codec_g711_deinit();
577# endif /* PJMEDIA_HAS_G711_CODEC */
578
Benny Prijono7ffd7752008-03-17 14:07:53 +0000579# if PJMEDIA_HAS_G722_CODEC
580 pjmedia_codec_g722_deinit();
581# endif /* PJMEDIA_HAS_G722_CODEC */
582
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000583# if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000584 pjmedia_codec_ipp_deinit();
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000585# endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000586
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000587# if PJMEDIA_HAS_PASSTHROUGH_CODECS
588 pjmedia_codec_passthrough_deinit();
589# endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
590
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000591# if PJMEDIA_HAS_L16_CODEC
592 pjmedia_codec_l16_deinit();
593# endif /* PJMEDIA_HAS_L16_CODEC */
594
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000595 pjmedia_endpt_destroy(pjsua_var.med_endpt);
596 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000597
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000598 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000599 // Not necessary, as pjmedia_snd_deinit() should have been called
600 // in pjmedia_endpt_destroy().
601 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000602 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000603
Benny Prijonode479562007-03-15 10:23:55 +0000604 /* Reset RTP port */
605 next_rtp_port = 0;
606
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000607 return PJ_SUCCESS;
608}
609
610
Benny Prijonoc97608e2007-03-23 16:34:20 +0000611/* Create normal UDP media transports */
612static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000613{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000614 unsigned i;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000615 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000616 pj_status_t status;
617
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000618 /* Create each media transport */
619 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
620
Benny Prijono617c5bc2007-04-02 19:51:21 +0000621 status = create_rtp_rtcp_sock(cfg, &skinfo);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000622 if (status != PJ_SUCCESS) {
623 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
624 status);
625 goto on_error;
626 }
Benny Prijonod8179652008-01-23 20:39:07 +0000627
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000628 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000629 &skinfo, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000630 &pjsua_var.calls[i].med_tp);
631 if (status != PJ_SUCCESS) {
632 pjsua_perror(THIS_FILE, "Unable to create media transport",
633 status);
634 goto on_error;
635 }
Benny Prijono00cae612006-07-31 15:19:36 +0000636
Benny Prijonod8179652008-01-23 20:39:07 +0000637 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
638 PJMEDIA_DIR_ENCODING,
639 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000640
Benny Prijonod8179652008-01-23 20:39:07 +0000641 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
642 PJMEDIA_DIR_DECODING,
643 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000644
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000645 }
646
Benny Prijonoc97608e2007-03-23 16:34:20 +0000647 return PJ_SUCCESS;
648
649on_error:
650 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
651 if (pjsua_var.calls[i].med_tp != NULL) {
652 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
653 pjsua_var.calls[i].med_tp = NULL;
654 }
655 }
656
657 return status;
658}
659
660
Benny Prijono096c56c2007-09-15 08:30:16 +0000661/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000662static void on_ice_complete(pjmedia_transport *tp,
663 pj_ice_strans_op op,
664 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000665{
Benny Prijonof76e1392008-06-06 14:51:48 +0000666 unsigned id;
Benny Prijono096c56c2007-09-15 08:30:16 +0000667 pj_bool_t found = PJ_FALSE;
668
Benny Prijono096c56c2007-09-15 08:30:16 +0000669 /* Find call which has this media transport */
670
671 PJSUA_LOCK();
672
Benny Prijonof76e1392008-06-06 14:51:48 +0000673 for (id=0; id<pjsua_var.ua_cfg.max_calls; ++id) {
674 if (pjsua_var.calls[id].med_tp == tp ||
675 pjsua_var.calls[id].med_orig == tp)
676 {
677 found = PJ_TRUE;
678 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000679 }
680 }
681
682 PJSUA_UNLOCK();
683
Benny Prijonof76e1392008-06-06 14:51:48 +0000684 if (!found)
685 return;
686
687 switch (op) {
688 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono224b4e22008-06-19 14:10:28 +0000689 pjsua_var.calls[id].med_tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000690 break;
691 case PJ_ICE_STRANS_OP_NEGOTIATION:
692 if (result != PJ_SUCCESS) {
693 pjsua_var.calls[id].media_st = PJSUA_CALL_MEDIA_ERROR;
694 pjsua_var.calls[id].media_dir = PJMEDIA_DIR_NONE;
695
696 if (pjsua_var.ua_cfg.cb.on_call_media_state) {
697 pjsua_var.ua_cfg.cb.on_call_media_state(id);
698 }
699 }
700 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000701 }
702}
703
704
Benny Prijonof76e1392008-06-06 14:51:48 +0000705/* Parse "HOST:PORT" format */
706static pj_status_t parse_host_port(const pj_str_t *host_port,
707 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000708{
Benny Prijonof76e1392008-06-06 14:51:48 +0000709 pj_str_t str_port;
710
711 str_port.ptr = pj_strchr(host_port, ':');
712 if (str_port.ptr != NULL) {
713 int iport;
714
715 host->ptr = host_port->ptr;
716 host->slen = (str_port.ptr - host->ptr);
717 str_port.ptr++;
718 str_port.slen = host_port->slen - host->slen - 1;
719 iport = (int)pj_strtoul(&str_port);
720 if (iport < 1 || iport > 65535)
721 return PJ_EINVAL;
722 *port = (pj_uint16_t)iport;
723 } else {
724 *host = *host_port;
725 *port = 0;
726 }
727
728 return PJ_SUCCESS;
729}
730
731/* Create ICE media transports (when ice is enabled) */
732static pj_status_t create_ice_media_transports(void)
733{
734 char stunip[PJ_INET6_ADDRSTRLEN];
735 pj_ice_strans_cfg ice_cfg;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000736 unsigned i;
737 pj_status_t status;
738
Benny Prijonoda9785b2007-04-02 20:43:06 +0000739 /* Make sure STUN server resolution has completed */
740 status = pjsua_resolve_stun_server(PJ_TRUE);
741 if (status != PJ_SUCCESS) {
742 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
743 return status;
744 }
745
Benny Prijonof76e1392008-06-06 14:51:48 +0000746 /* Create ICE stream transport configuration */
747 pj_ice_strans_cfg_default(&ice_cfg);
748 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
749 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
750 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
751
752 ice_cfg.af = pj_AF_INET();
753 ice_cfg.resolver = pjsua_var.resolver;
754
755 /* Configure STUN settings */
756 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
757 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
758 ice_cfg.stun.server = pj_str(stunip);
759 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
760 }
761 ice_cfg.stun.no_host_cands = pjsua_var.media_cfg.ice_no_host_cands;
762
763 /* Configure TURN settings */
764 if (pjsua_var.media_cfg.enable_turn) {
765 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
766 &ice_cfg.turn.server,
767 &ice_cfg.turn.port);
768 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
769 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
770 return PJ_EINVAL;
771 }
772 if (ice_cfg.turn.port == 0)
773 ice_cfg.turn.port = 3479;
774 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
775 pj_memcpy(&ice_cfg.turn.auth_cred,
776 &pjsua_var.media_cfg.turn_auth_cred,
777 sizeof(ice_cfg.turn.auth_cred));
778 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000779
Benny Prijonoc97608e2007-03-23 16:34:20 +0000780 /* Create each media transport */
781 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono096c56c2007-09-15 08:30:16 +0000782 pjmedia_ice_cb ice_cb;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000783 char name[32];
Benny Prijonof76e1392008-06-06 14:51:48 +0000784 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000785
Benny Prijono096c56c2007-09-15 08:30:16 +0000786 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
787 ice_cb.on_ice_complete = &on_ice_complete;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000788 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i);
Benny Prijono224b4e22008-06-19 14:10:28 +0000789 pjsua_var.calls[i].med_tp_ready = PJ_EPENDING;
Benny Prijonof76e1392008-06-06 14:51:48 +0000790
791 comp_cnt = 1;
Benny Prijono551af422008-08-07 09:55:52 +0000792 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
Benny Prijonof76e1392008-06-06 14:51:48 +0000793 ++comp_cnt;
794
795 status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
796 &ice_cfg, &ice_cb,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000797 &pjsua_var.calls[i].med_tp);
798 if (status != PJ_SUCCESS) {
799 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
800 status);
801 goto on_error;
802 }
803
Benny Prijonof76e1392008-06-06 14:51:48 +0000804 /* Wait until transport is initialized, or time out */
805 PJSUA_UNLOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000806 while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000807 pjsua_handle_events(100);
808 }
809 PJSUA_LOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000810 if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000811 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
Benny Prijono224b4e22008-06-19 14:10:28 +0000812 pjsua_var.calls[i].med_tp_ready);
813 status = pjsua_var.calls[i].med_tp_ready;
Benny Prijonof76e1392008-06-06 14:51:48 +0000814 goto on_error;
815 }
816
Benny Prijonod8179652008-01-23 20:39:07 +0000817 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
818 PJMEDIA_DIR_ENCODING,
819 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono11da9bc2007-09-15 08:55:00 +0000820
Benny Prijonod8179652008-01-23 20:39:07 +0000821 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
822 PJMEDIA_DIR_DECODING,
823 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000824 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000825
826 return PJ_SUCCESS;
827
828on_error:
829 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
830 if (pjsua_var.calls[i].med_tp != NULL) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000831 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000832 pjsua_var.calls[i].med_tp = NULL;
833 }
834 }
835
Benny Prijonoc97608e2007-03-23 16:34:20 +0000836 return status;
837}
838
839
840/*
841 * Create UDP media transports for all the calls. This function creates
842 * one UDP media transport for each call.
843 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +0000844PJ_DEF(pj_status_t) pjsua_media_transports_create(
845 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000846{
847 pjsua_transport_config cfg;
848 unsigned i;
849 pj_status_t status;
850
851
852 /* Make sure pjsua_init() has been called */
853 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
854
855 PJSUA_LOCK();
856
857 /* Delete existing media transports */
858 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono40860c32008-09-04 13:55:33 +0000859 if (pjsua_var.calls[i].med_tp != NULL &&
860 pjsua_var.calls[i].med_tp_auto_del)
861 {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000862 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
863 pjsua_var.calls[i].med_tp = NULL;
Nanang Izzuddind704a8b2008-09-23 16:34:07 +0000864 pjsua_var.calls[i].med_orig = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000865 }
866 }
867
868 /* Copy config */
869 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
870
Benny Prijono40860c32008-09-04 13:55:33 +0000871 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000872 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000873 status = create_ice_media_transports();
Benny Prijonoc97608e2007-03-23 16:34:20 +0000874 } else {
875 status = create_udp_media_transports(&cfg);
876 }
877
Benny Prijono40860c32008-09-04 13:55:33 +0000878 /* Set media transport auto_delete to True */
879 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
880 pjsua_var.calls[i].med_tp_auto_del = PJ_TRUE;
881 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000882
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000883 PJSUA_UNLOCK();
884
885 return status;
886}
887
Benny Prijono40860c32008-09-04 13:55:33 +0000888/*
889 * Attach application's created media transports.
890 */
891PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
892 unsigned count,
893 pj_bool_t auto_delete)
894{
895 unsigned i;
896
897 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
898
899 /* Assign the media transports */
900 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
901 if (pjsua_var.calls[i].med_tp != NULL &&
902 pjsua_var.calls[i].med_tp_auto_del)
903 {
904 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
905 }
906
907 pjsua_var.calls[i].med_tp = tp[i].transport;
908 pjsua_var.calls[i].med_tp_auto_del = auto_delete;
909 }
910
911 return PJ_SUCCESS;
912}
913
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000914
Benny Prijonoc97608e2007-03-23 16:34:20 +0000915pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
Benny Prijonod8179652008-01-23 20:39:07 +0000916 pjsip_role_e role,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000917 int security_level,
Benny Prijono224b4e22008-06-19 14:10:28 +0000918 pj_pool_t *tmp_pool,
919 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000920 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000921{
922 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono224b4e22008-06-19 14:10:28 +0000923 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000924
Benny Prijonod8179652008-01-23 20:39:07 +0000925#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
926 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
927 pjmedia_srtp_setting srtp_opt;
Benny Prijonoa310bd22008-06-27 21:19:44 +0000928 pjmedia_transport *srtp = NULL;
Benny Prijonod8179652008-01-23 20:39:07 +0000929#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000930
Benny Prijonod8179652008-01-23 20:39:07 +0000931 PJ_UNUSED_ARG(role);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000932
Benny Prijonod8179652008-01-23 20:39:07 +0000933 /* Return error if media transport has not been created yet
934 * (e.g. application is starting)
935 */
936 if (call->med_tp == NULL) {
Benny Prijono03789052008-09-16 14:30:50 +0000937 if (sip_err_code)
938 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
Benny Prijonod8179652008-01-23 20:39:07 +0000939 return PJ_EBUSY;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000940 }
941
Benny Prijonod8179652008-01-23 20:39:07 +0000942#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Benny Prijono53a7c702008-04-14 02:57:29 +0000943 /* This function may be called when SRTP transport already exists
944 * (e.g: in re-invite, update), don't need to destroy/re-create.
945 */
946 if (!call->med_orig || call->med_tp == call->med_orig) {
947
948 /* Check if SRTP requires secure signaling */
949 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
950 if (security_level < acc->cfg.srtp_secure_signaling) {
951 if (sip_err_code)
952 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
953 return PJSIP_ESESSIONINSECURE;
954 }
Benny Prijonod8179652008-01-23 20:39:07 +0000955 }
Benny Prijonod8179652008-01-23 20:39:07 +0000956
Benny Prijono53a7c702008-04-14 02:57:29 +0000957 /* Always create SRTP adapter */
958 pjmedia_srtp_setting_default(&srtp_opt);
959 srtp_opt.close_member_tp = PJ_FALSE;
Nanang Izzuddin4375f902008-06-26 19:12:09 +0000960 /* If media session has been ever established, let's use remote's
961 * preference in SRTP usage policy, especially when it is stricter.
962 */
963 if (call->rem_srtp_use > acc->cfg.use_srtp)
964 srtp_opt.use = call->rem_srtp_use;
965 else
966 srtp_opt.use = acc->cfg.use_srtp;
967
Benny Prijono53a7c702008-04-14 02:57:29 +0000968 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
969 call->med_tp,
970 &srtp_opt, &srtp);
971 if (status != PJ_SUCCESS) {
972 if (sip_err_code)
973 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
974 return status;
975 }
Benny Prijonod8179652008-01-23 20:39:07 +0000976
Benny Prijono53a7c702008-04-14 02:57:29 +0000977 /* Set SRTP as current media transport */
978 call->med_orig = call->med_tp;
979 call->med_tp = srtp;
980 }
Benny Prijonod8179652008-01-23 20:39:07 +0000981#else
982 call->med_orig = call->med_tp;
983 PJ_UNUSED_ARG(security_level);
984#endif
985
Benny Prijonoa310bd22008-06-27 21:19:44 +0000986 /* Find out which media line in SDP that we support. If we are offerer,
987 * audio will be at index 0 in SDP.
988 */
989 if (rem_sdp == 0) {
990 call->audio_idx = 0;
991 }
992 /* Otherwise find out the candidate audio media line in SDP */
993 else {
994 unsigned i;
995 pj_bool_t srtp_active;
996
997#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
998 srtp_active = acc->cfg.use_srtp && srtp != NULL;
999#else
1000 srtp_active = PJ_FALSE;
1001#endif
1002
1003 /* Media count must have been checked */
1004 pj_assert(rem_sdp->media_count != 0);
1005
1006 for (i=0; i<rem_sdp->media_count; ++i) {
1007 const pjmedia_sdp_media *m = rem_sdp->media[i];
1008
1009 /* Skip if media is not audio */
1010 if (pj_stricmp2(&m->desc.media, "audio") != 0)
1011 continue;
1012
1013 /* Skip if media is disabled */
1014 if (m->desc.port == 0)
1015 continue;
1016
1017 /* Skip if transport is not supported */
1018 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") != 0 &&
1019 pj_stricmp2(&m->desc.transport, "RTP/SAVP") != 0)
1020 {
1021 continue;
1022 }
1023
1024 if (call->audio_idx == -1) {
1025 call->audio_idx = i;
1026 } else {
1027 /* We've found multiple candidates. This could happen
1028 * e.g. when remote is offering both RTP/AVP and RTP/AVP,
1029 * or when remote for some reason offers two audio.
1030 */
1031
1032 if (srtp_active &&
1033 pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0)
1034 {
1035 /* Prefer RTP/SAVP when our media transport is SRTP */
1036 call->audio_idx = i;
1037 } else if (!srtp_active &&
1038 pj_stricmp2(&m->desc.transport, "RTP/AVP")==0)
1039 {
1040 /* Prefer RTP/AVP when our media transport is NOT SRTP */
1041 call->audio_idx = i;
1042 }
1043 }
1044 }
1045 }
1046
1047 /* Reject offer if we couldn't find a good m=audio line in offer */
1048 if (call->audio_idx < 0) {
Benny Prijonoab8dba92008-06-27 21:59:15 +00001049 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001050 pjsua_media_channel_deinit(call_id);
Benny Prijonoab8dba92008-06-27 21:59:15 +00001051 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001052 }
1053
1054 PJ_LOG(4,(THIS_FILE, "Media index %d selected for call %d",
1055 call->audio_idx, call->index));
1056
Benny Prijono224b4e22008-06-19 14:10:28 +00001057 /* Create the media transport */
1058 status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001059 rem_sdp, call->audio_idx);
Benny Prijono224b4e22008-06-19 14:10:28 +00001060 if (status != PJ_SUCCESS) {
1061 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1062 pjsua_media_channel_deinit(call_id);
1063 return status;
1064 }
1065
1066 call->med_tp_st = PJSUA_MED_TP_INIT;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001067 return PJ_SUCCESS;
1068}
1069
1070pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1071 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001072 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001073 pjmedia_sdp_session **p_sdp,
1074 int *sip_status_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001075{
Benny Prijonoa310bd22008-06-27 21:19:44 +00001076 enum { MAX_MEDIA = 1 };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001077 pjmedia_sdp_session *sdp;
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001078 pjmedia_transport_info tpinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001079 pjsua_call *call = &pjsua_var.calls[call_id];
1080 pj_status_t status;
1081
Benny Prijono55e82352007-05-10 20:49:08 +00001082 /* Return error if media transport has not been created yet
1083 * (e.g. application is starting)
1084 */
1085 if (call->med_tp == NULL) {
1086 return PJ_EBUSY;
1087 }
1088
Benny Prijonoa310bd22008-06-27 21:19:44 +00001089 /* Media index must have been determined before */
1090 pj_assert(call->audio_idx != -1);
1091
Benny Prijono224b4e22008-06-19 14:10:28 +00001092 /* Create media if it's not created. This could happen when call is
1093 * currently on-hold
1094 */
1095 if (call->med_tp_st == PJSUA_MED_TP_IDLE) {
1096 pjsip_role_e role;
1097 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1098 status = pjsua_media_channel_init(call_id, role, call->secure_level,
1099 pool, rem_sdp, sip_status_code);
1100 if (status != PJ_SUCCESS)
1101 return status;
1102 }
1103
Benny Prijono617c5bc2007-04-02 19:51:21 +00001104 /* Get media socket info */
Benny Prijono734fc2d2008-03-17 16:05:35 +00001105 pjmedia_transport_info_init(&tpinfo);
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001106 pjmedia_transport_get_info(call->med_tp, &tpinfo);
Benny Prijono617c5bc2007-04-02 19:51:21 +00001107
1108 /* Create SDP */
Benny Prijonod8179652008-01-23 20:39:07 +00001109 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA,
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001110 &tpinfo.sock_info, &sdp);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001111 if (status != PJ_SUCCESS) {
1112 if (sip_status_code) *sip_status_code = 500;
Benny Prijono224b4e22008-06-19 14:10:28 +00001113 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001114 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001115
Benny Prijonoa310bd22008-06-27 21:19:44 +00001116 /* If we're answering and the selected media is not the first media
1117 * in SDP, then fill in the unselected media with with zero port.
1118 * Otherwise we'll crash in transport_encode_sdp() because the media
1119 * lines are not aligned between offer and answer.
1120 */
1121 if (rem_sdp && call->audio_idx != 0) {
1122 unsigned i;
1123
1124 for (i=0; i<rem_sdp->media_count; ++i) {
1125 const pjmedia_sdp_media *rem_m = rem_sdp->media[i];
1126 pjmedia_sdp_media *m;
1127 const pjmedia_sdp_attr *a;
1128
1129 if ((int)i == call->audio_idx)
1130 continue;
1131
1132 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1133 pj_strdup(pool, &m->desc.media, &rem_m->desc.media);
1134 pj_strdup(pool, &m->desc.transport, &rem_m->desc.transport);
1135 m->desc.port = 0;
1136
1137 /* Add one format, copy from the offer. And copy the corresponding
1138 * rtpmap and fmtp attributes too.
1139 */
1140 m->desc.fmt_count = 1;
1141 pj_strdup(pool, &m->desc.fmt[0], &rem_m->desc.fmt[0]);
1142 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1143 "rtpmap", &m->desc.fmt[0])) != NULL)
1144 {
1145 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1146 }
1147 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1148 "fmtp", &m->desc.fmt[0])) != NULL)
1149 {
1150 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1151 }
1152
1153 if (i==sdp->media_count)
1154 sdp->media[sdp->media_count++] = m;
1155 else {
1156 pj_array_insert(sdp->media, sizeof(sdp->media[0]),
1157 sdp->media_count, i, &m);
1158 ++sdp->media_count;
1159 }
1160 }
1161 }
1162
Benny Prijono6ba8c542007-10-16 01:34:14 +00001163 /* Add NAT info in the SDP */
1164 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1165 pjmedia_sdp_attr *a;
1166 pj_str_t value;
1167 char nat_info[80];
1168
1169 value.ptr = nat_info;
1170 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1171 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1172 "%d", pjsua_var.nat_type);
1173 } else {
1174 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1175 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1176 "%d %s",
1177 pjsua_var.nat_type,
1178 type_name);
1179 }
1180
1181 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1182
1183 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1184
1185 }
1186
Benny Prijonod8179652008-01-23 20:39:07 +00001187 /* Give the SDP to media transport */
Benny Prijono224b4e22008-06-19 14:10:28 +00001188 status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001189 call->audio_idx);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001190 if (status != PJ_SUCCESS) {
1191 if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono224b4e22008-06-19 14:10:28 +00001192 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001193 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001194
1195 *p_sdp = sdp;
1196 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001197}
1198
1199
1200static void stop_media_session(pjsua_call_id call_id)
1201{
1202 pjsua_call *call = &pjsua_var.calls[call_id];
1203
1204 if (call->conf_slot != PJSUA_INVALID_ID) {
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001205 if (pjsua_var.mconf) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001206 pjsua_conf_remove_port(call->conf_slot);
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001207 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001208 call->conf_slot = PJSUA_INVALID_ID;
1209 }
1210
1211 if (call->session) {
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001212 pjmedia_rtcp_stat stat;
1213
Nanang Izzuddin437d77c2008-08-26 18:04:15 +00001214 if (pjmedia_session_get_stream_stat(call->session, 0, &stat)
1215 == PJ_SUCCESS)
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001216 {
1217 /* Save RTP timestamp & sequence, so when media session is
1218 * restarted, those values will be restored as the initial
1219 * RTP timestamp & sequence of the new media session. So in
1220 * the same call session, RTP timestamp and sequence are
1221 * guaranteed to be contigue.
1222 */
1223 call->rtp_tx_seq_ts_set = 1 | (1 << 1);
1224 call->rtp_tx_seq = stat.rtp_tx_last_seq;
1225 call->rtp_tx_ts = stat.rtp_tx_last_ts;
1226 }
1227
Benny Prijonofc13bf62008-02-20 08:56:15 +00001228 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1229 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0);
1230 }
1231
Benny Prijonoc97608e2007-03-23 16:34:20 +00001232 pjmedia_session_destroy(call->session);
1233 call->session = NULL;
1234
1235 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
1236 call_id));
1237
1238 }
1239
1240 call->media_st = PJSUA_CALL_MEDIA_NONE;
1241}
1242
1243pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1244{
1245 pjsua_call *call = &pjsua_var.calls[call_id];
1246
1247 stop_media_session(call_id);
1248
Benny Prijono224b4e22008-06-19 14:10:28 +00001249 if (call->med_tp_st != PJSUA_MED_TP_IDLE) {
1250 pjmedia_transport_media_stop(call->med_tp);
1251 call->med_tp_st = PJSUA_MED_TP_IDLE;
1252 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001253
Benny Prijono311b63f2008-07-14 11:31:40 +00001254 if (call->med_orig && call->med_tp && call->med_tp != call->med_orig) {
Benny Prijonod8179652008-01-23 20:39:07 +00001255 pjmedia_transport_close(call->med_tp);
1256 call->med_tp = call->med_orig;
1257 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00001258
1259 check_snd_dev_idle();
1260
Benny Prijonoc97608e2007-03-23 16:34:20 +00001261 return PJ_SUCCESS;
1262}
1263
1264
1265/*
1266 * DTMF callback from the stream.
1267 */
1268static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1269 int digit)
1270{
1271 PJ_UNUSED_ARG(strm);
1272
Benny Prijono0c068262008-02-14 14:38:52 +00001273 /* For discussions about call mutex protection related to this
1274 * callback, please see ticket #460:
1275 * http://trac.pjsip.org/repos/ticket/460#comment:4
1276 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001277 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1278 pjsua_call_id call_id;
1279
Benny Prijonod8179652008-01-23 20:39:07 +00001280 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001281 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1282 }
1283}
1284
1285
1286pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijono224b4e22008-06-19 14:10:28 +00001287 const pjmedia_sdp_session *local_sdp,
Benny Prijonodbce2cf2007-03-28 16:24:00 +00001288 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001289{
1290 int prev_media_st = 0;
1291 pjsua_call *call = &pjsua_var.calls[call_id];
1292 pjmedia_session_info sess_info;
Benny Prijono91e567e2007-12-28 08:51:58 +00001293 pjmedia_stream_info *si = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001294 pjmedia_port *media_port;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001295 pj_status_t status;
1296
1297 /* Destroy existing media session, if any. */
1298 prev_media_st = call->media_st;
1299 stop_media_session(call->index);
1300
1301 /* Create media session info based on SDP parameters.
Benny Prijonoc97608e2007-03-23 16:34:20 +00001302 */
1303 status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
1304 pjsua_var.med_endpt,
Benny Prijono91e567e2007-12-28 08:51:58 +00001305 PJMEDIA_MAX_SDP_MEDIA, &sess_info,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001306 local_sdp, remote_sdp);
1307 if (status != PJ_SUCCESS)
1308 return status;
1309
Benny Prijonoa310bd22008-06-27 21:19:44 +00001310 /* Find which session is audio */
1311 PJ_ASSERT_RETURN(call->audio_idx != -1, PJ_EBUG);
1312 PJ_ASSERT_RETURN(call->audio_idx < (int)sess_info.stream_cnt, PJ_EBUG);
1313 si = &sess_info.stream_info[call->audio_idx];
Benny Prijono91e567e2007-12-28 08:51:58 +00001314
1315 /* Reset session info with only one media stream */
1316 sess_info.stream_cnt = 1;
1317 if (si != &sess_info.stream_info[0])
1318 pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001319
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001320 /* Check if no media is active */
Benny Prijono91e567e2007-12-28 08:51:58 +00001321 if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001322 {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001323 /* Call media state */
1324 call->media_st = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001325
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001326 /* Call media direction */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001327 call->media_dir = PJMEDIA_DIR_NONE;
1328
Benny Prijonod8179652008-01-23 20:39:07 +00001329 /* Shutdown transport's session */
1330 pjmedia_transport_media_stop(call->med_tp);
Benny Prijono224b4e22008-06-19 14:10:28 +00001331 call->med_tp_st = PJSUA_MED_TP_IDLE;
Benny Prijono667952e2007-04-02 19:27:54 +00001332
Benny Prijonoc97608e2007-03-23 16:34:20 +00001333 /* No need because we need keepalive? */
1334
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001335 /* Close upper entry of transport stack */
1336 if (call->med_orig && (call->med_tp != call->med_orig)) {
1337 pjmedia_transport_close(call->med_tp);
1338 call->med_tp = call->med_orig;
1339 }
1340
Benny Prijonoc97608e2007-03-23 16:34:20 +00001341 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001342 pjmedia_transport_info tp_info;
1343
Benny Prijono224b4e22008-06-19 14:10:28 +00001344 /* Start/restart media transport */
Benny Prijonod8179652008-01-23 20:39:07 +00001345 status = pjmedia_transport_media_start(call->med_tp,
1346 call->inv->pool,
1347 local_sdp, remote_sdp, 0);
1348 if (status != PJ_SUCCESS)
1349 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001350
Benny Prijono224b4e22008-06-19 14:10:28 +00001351 call->med_tp_st = PJSUA_MED_TP_RUNNING;
1352
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001353 /* Get remote SRTP usage policy */
1354 pjmedia_transport_info_init(&tp_info);
1355 pjmedia_transport_get_info(call->med_tp, &tp_info);
1356 if (tp_info.specific_info_cnt > 0) {
1357 int i;
1358 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1359 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1360 {
1361 pjmedia_srtp_info *srtp_info =
1362 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1363
1364 call->rem_srtp_use = srtp_info->peer_use;
1365 break;
1366 }
1367 }
1368 }
1369
Benny Prijonoc97608e2007-03-23 16:34:20 +00001370 /* Override ptime, if this option is specified. */
1371 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001372 si->param->setting.frm_per_pkt = (pj_uint8_t)
1373 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1374 if (si->param->setting.frm_per_pkt == 0)
1375 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001376 }
1377
1378 /* Disable VAD, if this option is specified. */
1379 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001380 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001381 }
1382
1383
1384 /* Optionally, application may modify other stream settings here
1385 * (such as jitter buffer parameters, codec ptime, etc.)
1386 */
Benny Prijono91e567e2007-12-28 08:51:58 +00001387 si->jb_init = pjsua_var.media_cfg.jb_init;
1388 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1389 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1390 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001391
Benny Prijono8147f402007-11-21 14:50:07 +00001392 /* Set SSRC */
Benny Prijono91e567e2007-12-28 08:51:58 +00001393 si->ssrc = call->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00001394
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001395 /* Set RTP timestamp & sequence, normally these value are intialized
1396 * automatically when stream session created, but for some cases (e.g:
1397 * call reinvite, call update) timestamp and sequence need to be kept
1398 * contigue.
1399 */
1400 si->rtp_ts = call->rtp_tx_ts;
1401 si->rtp_seq = call->rtp_tx_seq;
1402 si->rtp_seq_ts_set = call->rtp_tx_seq_ts_set;
1403
Benny Prijonoc97608e2007-03-23 16:34:20 +00001404 /* Create session based on session info. */
1405 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
1406 &call->med_tp,
1407 call, &call->session );
1408 if (status != PJ_SUCCESS) {
1409 return status;
1410 }
1411
1412 /* If DTMF callback is installed by application, install our
1413 * callback to the session.
1414 */
1415 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1416 pjmedia_session_set_dtmf_callback(call->session, 0,
1417 &dtmf_callback,
Benny Prijonod8179652008-01-23 20:39:07 +00001418 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001419 }
1420
1421 /* Get the port interface of the first stream in the session.
1422 * We need the port interface to add to the conference bridge.
1423 */
1424 pjmedia_session_get_port(call->session, 0, &media_port);
1425
Benny Prijonofc13bf62008-02-20 08:56:15 +00001426 /* Notify application about stream creation.
1427 * Note: application may modify media_port to point to different
1428 * media port
1429 */
1430 if (pjsua_var.ua_cfg.cb.on_stream_created) {
1431 pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session,
1432 0, &media_port);
1433 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001434
1435 /*
1436 * Add the call to conference bridge.
1437 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001438 {
1439 char tmp[PJSIP_MAX_URL_SIZE];
1440 pj_str_t port_name;
1441
1442 port_name.ptr = tmp;
1443 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1444 call->inv->dlg->remote.info->uri,
1445 tmp, sizeof(tmp));
1446 if (port_name.slen < 1) {
1447 port_name = pj_str("call");
1448 }
1449 status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
1450 media_port,
1451 &port_name,
1452 (unsigned*)&call->conf_slot);
1453 if (status != PJ_SUCCESS) {
1454 return status;
1455 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001456 }
1457
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001458 /* Call media direction */
Benny Prijono91e567e2007-12-28 08:51:58 +00001459 call->media_dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001460
1461 /* Call media state */
1462 if (call->local_hold)
1463 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
1464 else if (call->media_dir == PJMEDIA_DIR_DECODING)
1465 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
1466 else
1467 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001468 }
1469
1470 /* Print info. */
1471 {
1472 char info[80];
1473 int info_len = 0;
1474 unsigned i;
1475
1476 for (i=0; i<sess_info.stream_cnt; ++i) {
1477 int len;
1478 const char *dir;
1479 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1480
1481 switch (strm_info->dir) {
1482 case PJMEDIA_DIR_NONE:
1483 dir = "inactive";
1484 break;
1485 case PJMEDIA_DIR_ENCODING:
1486 dir = "sendonly";
1487 break;
1488 case PJMEDIA_DIR_DECODING:
1489 dir = "recvonly";
1490 break;
1491 case PJMEDIA_DIR_ENCODING_DECODING:
1492 dir = "sendrecv";
1493 break;
1494 default:
1495 dir = "unknown";
1496 break;
1497 }
1498 len = pj_ansi_sprintf( info+info_len,
1499 ", stream #%d: %.*s (%s)", i,
1500 (int)strm_info->fmt.encoding_name.slen,
1501 strm_info->fmt.encoding_name.ptr,
1502 dir);
1503 if (len > 0)
1504 info_len += len;
1505 }
1506 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1507 }
1508
1509 return PJ_SUCCESS;
1510}
1511
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001512#if PJMEDIA_CONF_USE_SWITCH_BOARD
1513
1514/*
1515 * Open sound device with extended setting.
1516 */
Benny Prijono10454dc2009-02-21 14:21:59 +00001517static pj_status_t open_snd_dev_ext(pjmedia_aud_param *param)
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001518{
1519 pjmedia_port *conf_port;
1520 pj_status_t status;
1521
Benny Prijono10454dc2009-02-21 14:21:59 +00001522 PJ_ASSERT_RETURN(param, PJ_EINVAL);
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001523
1524 /* Check if NULL sound device is used */
Benny Prijono10454dc2009-02-21 14:21:59 +00001525 if (NULL_SND_DEV_ID==param->rec_id || NULL_SND_DEV_ID==param->play_id) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001526 return pjsua_set_null_snd_dev();
1527 }
1528
1529 /* Close existing sound port */
1530 close_snd_dev();
1531
1532 /* Create memory pool for sound device. */
1533 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
1534 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
1535
1536
1537 /* Get the port0 of the conference bridge. */
1538 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1539 pj_assert(conf_port != NULL);
1540
1541 PJ_LOG(4,(THIS_FILE, "Opening sound device @%d/%d/%s",
Benny Prijono10454dc2009-02-21 14:21:59 +00001542 param->clock_rate, param->channel_count,
1543 (param->ext_fmt.id==PJMEDIA_FORMAT_L16?"pcm":"encoded")));
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001544
1545 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
Benny Prijono10454dc2009-02-21 14:21:59 +00001546 param, &pjsua_var.snd_port);
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001547
1548 /* Update port 0 info when sound dev opened successfully. */
1549 if (status == PJ_SUCCESS) {
Benny Prijono10454dc2009-02-21 14:21:59 +00001550 pj_memcpy(&conf_port->info.format, &param->ext_fmt,
1551 sizeof(conf_port->info.format));
1552 conf_port->info.clock_rate = param->clock_rate;
1553 conf_port->info.samples_per_frame = param->samples_per_frame;
1554 conf_port->info.channel_count = param->channel_count;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001555 conf_port->info.bits_per_sample = 16;
1556 } else {
1557 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
1558 return status;
1559 }
1560
1561 /* Connect sound port to the bridge */
1562 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
1563 conf_port );
1564 if (status != PJ_SUCCESS) {
1565 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
1566 "sound device", status);
1567 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1568 pjsua_var.snd_port = NULL;
1569 return status;
1570 }
1571
1572 /* Save the device IDs */
Benny Prijono10454dc2009-02-21 14:21:59 +00001573 pjsua_var.cap_dev = param->rec_id;
1574 pjsua_var.play_dev = param->play_id;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001575
1576 /* Update sound device name. */
Benny Prijono10454dc2009-02-21 14:21:59 +00001577 {
1578 pjmedia_aud_dev_info play_info;
1579 pjmedia_aud_stream *strm;
1580 pjmedia_aud_param si;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001581 pj_str_t tmp;
1582
1583 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono10454dc2009-02-21 14:21:59 +00001584 pjmedia_aud_stream_get_param(strm, &si);
1585 if (pjmedia_aud_dev_get_info(si.play_id, &play_info)==PJ_SUCCESS) {
1586 pjmedia_conf_set_port0_name(pjsua_var.mconf,
1587 pj_cstr(&tmp, play_info.name));
1588 }
1589 };
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001590
1591 return PJ_SUCCESS;
1592}
1593
1594#endif
1595
1596
1597/* Close existing sound device */
1598static void close_snd_dev(void)
1599{
1600 /* Close sound device */
1601 if (pjsua_var.snd_port) {
Benny Prijono10454dc2009-02-21 14:21:59 +00001602 pjmedia_aud_dev_info cap_info, play_info;
1603 pjmedia_aud_stream *strm;
1604 pjmedia_aud_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001605
1606 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono10454dc2009-02-21 14:21:59 +00001607 pjmedia_aud_stream_get_param(strm, &param);
1608
1609 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
1610 cap_info.name[0] = '\0';
1611 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
1612 play_info.name[0] = '\0';
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001613
1614 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
1615 "%s sound capture device",
1616 play_info.name, cap_info.name));
1617
1618 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
1619 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1620 pjsua_var.snd_port = NULL;
1621 }
1622
1623 /* Close null sound device */
1624 if (pjsua_var.null_snd) {
1625 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
1626 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
1627 pjsua_var.null_snd = NULL;
1628 }
1629
1630 if (pjsua_var.snd_pool)
1631 pj_pool_release(pjsua_var.snd_pool);
1632 pjsua_var.snd_pool = NULL;
1633}
1634
Benny Prijonoc97608e2007-03-23 16:34:20 +00001635
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001636/*
1637 * Get maxinum number of conference ports.
1638 */
1639PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1640{
1641 return pjsua_var.media_cfg.max_media_ports;
1642}
1643
1644
1645/*
1646 * Get current number of active ports in the bridge.
1647 */
1648PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1649{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001650 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001651 unsigned count = PJ_ARRAY_SIZE(ports);
1652 pj_status_t status;
1653
1654 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1655 if (status != PJ_SUCCESS)
1656 count = 0;
1657
1658 return count;
1659}
1660
1661
1662/*
1663 * Enumerate all conference ports.
1664 */
1665PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1666 unsigned *count)
1667{
1668 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1669}
1670
1671
1672/*
1673 * Get information about the specified conference port
1674 */
1675PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1676 pjsua_conf_port_info *info)
1677{
1678 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001679 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001680 pj_status_t status;
1681
1682 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1683 if (status != PJ_SUCCESS)
1684 return status;
1685
Benny Prijonoac623b32006-07-03 15:19:31 +00001686 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001687 info->slot_id = id;
1688 info->name = cinfo.name;
1689 info->clock_rate = cinfo.clock_rate;
1690 info->channel_count = cinfo.channel_count;
1691 info->samples_per_frame = cinfo.samples_per_frame;
1692 info->bits_per_sample = cinfo.bits_per_sample;
1693
1694 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001695 info->listener_cnt = cinfo.listener_cnt;
1696 for (i=0; i<cinfo.listener_cnt; ++i) {
1697 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001698 }
1699
1700 return PJ_SUCCESS;
1701}
1702
1703
1704/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001705 * Add arbitrary media port to PJSUA's conference bridge.
1706 */
1707PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1708 pjmedia_port *port,
1709 pjsua_conf_port_id *p_id)
1710{
1711 pj_status_t status;
1712
1713 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1714 port, NULL, (unsigned*)p_id);
1715 if (status != PJ_SUCCESS) {
1716 if (p_id)
1717 *p_id = PJSUA_INVALID_ID;
1718 }
1719
1720 return status;
1721}
1722
1723
1724/*
1725 * Remove arbitrary slot from the conference bridge.
1726 */
1727PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1728{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001729 pj_status_t status;
1730
1731 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1732 check_snd_dev_idle();
1733
1734 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00001735}
1736
1737
1738/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001739 * Establish unidirectional media flow from souce to sink.
1740 */
1741PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1742 pjsua_conf_port_id sink)
1743{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001744 /* If sound device idle timer is active, cancel it first. */
1745 if (pjsua_var.snd_idle_timer.id) {
1746 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
1747 pjsua_var.snd_idle_timer.id = PJ_FALSE;
1748 }
1749
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001750#if PJMEDIA_CONF_USE_SWITCH_BOARD
1751
1752 /* Check if sound device need to be reopened, i.e: its attributes
1753 * (format, clock rate, channel count) must match to peer's.
1754 * Note that sound device can be reopened only if it doesn't have
1755 * any connection.
1756 */
1757 do {
1758 pjmedia_conf_port_info port0_info;
1759 pjmedia_conf_port_info peer_info;
1760 unsigned peer_id;
1761 pj_bool_t need_reopen = PJ_FALSE;
1762 pj_status_t status;
1763
1764 peer_id = (source!=0)? source : sink;
1765 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
1766 &peer_info);
1767 pj_assert(status == PJ_SUCCESS);
1768
1769 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
1770 pj_assert(status == PJ_SUCCESS);
1771
1772 /* Check if sound device is instantiated. */
1773 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1774 !pjsua_var.no_snd);
1775
1776 /* Check if sound device need to reopen because it needs to modify
1777 * settings to match its peer. Sound device must be idle in this case
1778 * though.
1779 */
1780 if (!need_reopen &&
1781 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
1782 {
1783 need_reopen = (peer_info.format.id != port0_info.format.id ||
1784 peer_info.format.bitrate != port0_info.format.bitrate ||
1785 peer_info.clock_rate != port0_info.clock_rate ||
1786 peer_info.channel_count != port0_info.channel_count);
1787 }
1788
1789 if (need_reopen) {
Benny Prijono10454dc2009-02-21 14:21:59 +00001790 pjmedia_aud_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001791
Benny Prijono10454dc2009-02-21 14:21:59 +00001792 status = pjmedia_aud_dev_default_param(pjsua_var.cap_dev, &param);
1793 if (status != PJ_SUCCESS) {
1794 pjsua_perror(THIS_FILE, "Error retrieving default audio "
1795 "device parameters", status);
1796 return status;
1797 }
1798
1799 param.dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
1800 param.rec_id = pjsua_var.cap_dev;
1801 param.play_id = pjsua_var.play_dev;
1802 param.clock_rate = peer_info.clock_rate;
1803 param.samples_per_frame = peer_info.samples_per_frame;
1804 param.channel_count = peer_info.channel_count;
1805 param.bits_per_sample = peer_info.bits_per_sample;
1806 /* Latency setting */
1807 param.flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
1808 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
1809 param.input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
1810 param.output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
1811 /* EC settings */
1812 if (pjsua_var.media_cfg.ec_tail_len) {
1813 param.flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
1814 param.ec_enabled = PJ_TRUE;
1815 param.ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
1816 } else {
1817 param.flags &= ~(PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
1818 }
1819 /* Format */
1820 param.flags |= (PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
1821 PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE);
1822 param.ext_fmt = peer_info.format;
1823 //param.plc = PJ_FALSE;
1824 param.out_route = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001825
Benny Prijono10454dc2009-02-21 14:21:59 +00001826 status = open_snd_dev_ext(&param);
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001827 if (status != PJ_SUCCESS) {
1828 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1829 return status;
1830 }
1831 }
1832 } while(0);
1833
1834#else
1835
Nanang Izzuddin68559c32008-06-13 17:01:46 +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;
1841
1842 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 }
1847 }
1848
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001849#endif
1850
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 Prijono10454dc2009-02-21 14:21:59 +00002325#if PJMEDIA_AUDIO_API==PJMEDIA_AUDIO_API_NEW_ONLY
2326PJ_DEF(pj_status_t) pjsua_enum_snd_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 Prijono10454dc2009-02-21 14:21:59 +00002347#else /* PJMEDIA_AUDIO_API */
2348PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
2349 unsigned *count)
2350{
2351 unsigned i, dev_count;
2352
2353 dev_count = pjmedia_aud_dev_count();
2354
2355 if (dev_count > *count) dev_count = *count;
2356 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
2357
2358 for (i=0; i<dev_count; ++i) {
2359 pjmedia_aud_dev_info ai;
2360 pj_status_t status;
2361
2362 status = pjmedia_aud_dev_get_info(i, &ai);
2363 if (status != PJ_SUCCESS)
2364 return status;
2365
2366 strncpy(info[i].name, ai.name, sizeof(info[i].name));
2367 info[i].name[sizeof(info[i].name)-1] = '\0';
2368 info[i].input_count = ai.input_count;
2369 info[i].output_count = ai.output_count;
2370 info[i].default_samples_per_sec = ai.default_samples_per_sec;
2371 }
2372
2373 *count = dev_count;
2374
2375 return PJ_SUCCESS;
2376}
2377#endif
2378
2379
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002380/*
2381 * Select or change sound device. Application may call this function at
2382 * any time to replace current sound device.
2383 */
2384PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
2385 int playback_dev)
2386{
2387 pjmedia_port *conf_port;
Benny Prijono10454dc2009-02-21 14:21:59 +00002388 pjmedia_aud_dev_info play_info;
2389 pjmedia_aud_param param;
Nanang Izzuddin6aa44952008-10-07 12:42:24 +00002390 unsigned clock_rates[] = {0, 44100, 48000, 32000, 16000, 8000};
Benny Prijono658a1c52006-10-11 21:56:16 +00002391 unsigned selected_clock_rate = 0;
Benny Prijono26056d82006-10-11 16:03:41 +00002392 unsigned i;
Benny Prijono10454dc2009-02-21 14:21:59 +00002393 pjmedia_aud_stream *strm;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002394 pj_str_t tmp;
Benny Prijono26056d82006-10-11 16:03:41 +00002395 pj_status_t status = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002396
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002397 /* Check if NULL sound device is used */
2398 if (NULL_SND_DEV_ID == capture_dev || NULL_SND_DEV_ID == playback_dev) {
2399 return pjsua_set_null_snd_dev();
2400 }
2401
Benny Prijono96e74f32009-02-22 12:00:12 +00002402 /* Normalize device ID with new convention about default device ID */
2403 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
2404 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
2405
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002406 /* Close existing sound port */
2407 close_snd_dev();
2408
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002409 /* Create memory pool for sound device. */
Benny Prijono10454dc2009-02-21 14:21:59 +00002410 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 1000, 1000);
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002411 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002412
Benny Prijono26056d82006-10-11 16:03:41 +00002413 /* Set default clock rate */
Benny Prijonof3758ee2008-02-26 15:32:16 +00002414 clock_rates[0] = pjsua_var.media_cfg.snd_clock_rate;
2415 if (clock_rates[0] == 0)
2416 clock_rates[0] = pjsua_var.media_cfg.clock_rate;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002417
Benny Prijono50f19b32008-03-11 13:15:43 +00002418 /* Get the port0 of the conference bridge. */
2419 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2420 pj_assert(conf_port != NULL);
2421
Benny Prijono10454dc2009-02-21 14:21:59 +00002422 /* Create default parameters for the device */
2423 status = pjmedia_aud_dev_default_param(capture_dev, &param);
2424 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00002425 pjsua_perror(THIS_FILE, "Error retrieving default audio "
2426 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00002427 return status;
2428 }
2429 param.dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
2430 param.rec_id = capture_dev;
2431 param.play_id = playback_dev;
2432 param.channel_count = pjsua_var.media_cfg.channel_count;
2433 /* Latency settings */
2434 param.flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
2435 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
2436 param.input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
2437 param.output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
2438 /* EC settings */
2439 if (pjsua_var.media_cfg.ec_tail_len) {
2440 param.flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
2441 param.ec_enabled = PJ_TRUE;
2442 param.ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2443 } else {
2444 param.flags &= ~(PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
2445 }
2446
Benny Prijono26056d82006-10-11 16:03:41 +00002447 /* Attempts to open the sound device with different clock rates */
2448 for (i=0; i<PJ_ARRAY_SIZE(clock_rates); ++i) {
2449 char errmsg[PJ_ERR_MSG_SIZE];
2450
2451 PJ_LOG(4,(THIS_FILE,
2452 "pjsua_set_snd_dev(): attempting to open devices "
2453 "@%d Hz", clock_rates[i]));
2454
Benny Prijono10454dc2009-02-21 14:21:59 +00002455 param.clock_rate = clock_rates[i];
2456 param.samples_per_frame = clock_rates[i] *
2457 pjsua_var.media_cfg.audio_frame_ptime *
2458 pjsua_var.media_cfg.channel_count / 1000;
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002459
Benny Prijono26056d82006-10-11 16:03:41 +00002460 /* Create the sound device. Sound port will start immediately. */
Benny Prijono10454dc2009-02-21 14:21:59 +00002461 status = pjmedia_snd_port_create2(pjsua_var.snd_pool, &param,
2462 &pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00002463
Benny Prijono658a1c52006-10-11 21:56:16 +00002464 if (status == PJ_SUCCESS) {
2465 selected_clock_rate = clock_rates[i];
Benny Prijono50f19b32008-03-11 13:15:43 +00002466
2467 /* If there's mismatch between sound port and conference's port,
2468 * create a resample port to bridge them.
2469 */
2470 if (selected_clock_rate != pjsua_var.media_cfg.clock_rate) {
2471 pjmedia_port *resample_port;
2472 unsigned resample_opt = 0;
2473
2474 if (pjsua_var.media_cfg.quality >= 3 &&
2475 pjsua_var.media_cfg.quality <= 4)
2476 {
Nanang Izzuddine85a1832008-05-29 11:03:23 +00002477 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
Benny Prijono50f19b32008-03-11 13:15:43 +00002478 }
2479 else if (pjsua_var.media_cfg.quality < 3) {
Nanang Izzuddine85a1832008-05-29 11:03:23 +00002480 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
Benny Prijono50f19b32008-03-11 13:15:43 +00002481 }
2482
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002483 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
Benny Prijono50f19b32008-03-11 13:15:43 +00002484 conf_port,
2485 selected_clock_rate,
2486 resample_opt,
2487 &resample_port);
2488 if (status != PJ_SUCCESS) {
2489 pj_strerror(status, errmsg, sizeof(errmsg));
2490 PJ_LOG(4, (THIS_FILE,
2491 "Error creating resample port, trying next "
2492 "clock rate",
2493 errmsg));
2494 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2495 pjsua_var.snd_port = NULL;
2496 continue;
2497 } else {
2498 conf_port = resample_port;
2499 break;
2500 }
2501
2502 } else {
2503 break;
2504 }
Benny Prijono658a1c52006-10-11 21:56:16 +00002505 }
Benny Prijono26056d82006-10-11 16:03:41 +00002506
2507 pj_strerror(status, errmsg, sizeof(errmsg));
Benny Prijono658a1c52006-10-11 21:56:16 +00002508 PJ_LOG(4, (THIS_FILE, "..failed: %s", errmsg));
Benny Prijono26056d82006-10-11 16:03:41 +00002509 }
2510
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002511 if (status != PJ_SUCCESS) {
2512 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
2513 return status;
2514 }
2515
Benny Prijono52a93912006-08-04 20:54:37 +00002516 /* Connect sound port to the bridge */
2517 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
2518 conf_port );
2519 if (status != PJ_SUCCESS) {
2520 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
2521 "sound device", status);
2522 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2523 pjsua_var.snd_port = NULL;
2524 return status;
2525 }
2526
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002527 /* Save the device IDs */
2528 pjsua_var.cap_dev = capture_dev;
2529 pjsua_var.play_dev = playback_dev;
2530
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002531 /* Update sound device name. */
2532 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono10454dc2009-02-21 14:21:59 +00002533 pjmedia_aud_stream_get_param(strm, &param);
2534 pjmedia_aud_dev_get_info(param.play_id, &play_info);
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002535
Benny Prijono10454dc2009-02-21 14:21:59 +00002536 if (param.clock_rate != pjsua_var.media_cfg.clock_rate) {
Benny Prijonof3758ee2008-02-26 15:32:16 +00002537 char tmp_buf[128];
2538 int tmp_buf_len = sizeof(tmp_buf);
2539
2540 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1, "%s (%dKHz)",
Benny Prijono10454dc2009-02-21 14:21:59 +00002541 play_info.name, param.clock_rate/1000);
Benny Prijonof3758ee2008-02-26 15:32:16 +00002542 pj_strset(&tmp, tmp_buf, tmp_buf_len);
2543 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
2544 } else {
2545 pjmedia_conf_set_port0_name(pjsua_var.mconf,
Benny Prijono10454dc2009-02-21 14:21:59 +00002546 pj_cstr(&tmp, play_info.name));
Benny Prijonof3758ee2008-02-26 15:32:16 +00002547 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002548
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002549 return PJ_SUCCESS;
2550}
2551
2552
2553/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00002554 * Get currently active sound devices. If sound devices has not been created
2555 * (for example when pjsua_start() is not called), it is possible that
2556 * the function returns PJ_SUCCESS with -1 as device IDs.
2557 */
2558PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
2559 int *playback_dev)
2560{
2561 if (capture_dev) {
2562 *capture_dev = pjsua_var.cap_dev;
2563 }
2564 if (playback_dev) {
2565 *playback_dev = pjsua_var.play_dev;
2566 }
2567
2568 return PJ_SUCCESS;
2569}
2570
2571
2572/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002573 * Use null sound device.
2574 */
2575PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
2576{
2577 pjmedia_port *conf_port;
2578 pj_status_t status;
2579
2580 /* Close existing sound device */
2581 close_snd_dev();
2582
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002583 /* Create memory pool for sound device. */
2584 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2585 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2586
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002587 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
2588
2589 /* Get the port0 of the conference bridge. */
2590 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2591 pj_assert(conf_port != NULL);
2592
2593 /* Create master port, connecting port0 of the conference bridge to
2594 * a null port.
2595 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002596 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002597 conf_port, 0, &pjsua_var.null_snd);
2598 if (status != PJ_SUCCESS) {
2599 pjsua_perror(THIS_FILE, "Unable to create null sound device",
2600 status);
2601 return status;
2602 }
2603
2604 /* Start the master port */
2605 status = pjmedia_master_port_start(pjsua_var.null_snd);
2606 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2607
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002608 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2609 pjsua_var.play_dev = NULL_SND_DEV_ID;
2610
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002611 return PJ_SUCCESS;
2612}
2613
2614
Benny Prijonoe909eac2006-07-27 22:04:56 +00002615
2616/*
2617 * Use no device!
2618 */
2619PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2620{
2621 /* Close existing sound device */
2622 close_snd_dev();
2623
2624 pjsua_var.no_snd = PJ_TRUE;
2625 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2626}
2627
2628
Benny Prijonof20687a2006-08-04 18:27:19 +00002629/*
2630 * Configure the AEC settings of the sound port.
2631 */
Benny Prijono5da50432006-08-07 10:24:52 +00002632PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00002633{
2634 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2635
2636 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00002637 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
2638 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00002639
2640 return PJ_SUCCESS;
2641}
2642
2643
2644/*
2645 * Get current AEC tail length.
2646 */
Benny Prijono22dfe592006-08-06 12:07:13 +00002647PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00002648{
2649 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2650 return PJ_SUCCESS;
2651}
2652
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002653
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002654/*****************************************************************************
2655 * Codecs.
2656 */
2657
2658/*
2659 * Enum all supported codecs in the system.
2660 */
2661PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
2662 unsigned *p_count )
2663{
2664 pjmedia_codec_mgr *codec_mgr;
2665 pjmedia_codec_info info[32];
2666 unsigned i, count, prio[32];
2667 pj_status_t status;
2668
2669 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2670 count = PJ_ARRAY_SIZE(info);
2671 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
2672 if (status != PJ_SUCCESS) {
2673 *p_count = 0;
2674 return status;
2675 }
2676
2677 if (count > *p_count) count = *p_count;
2678
2679 for (i=0; i<count; ++i) {
2680 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
2681 id[i].codec_id = pj_str(id[i].buf_);
2682 id[i].priority = (pj_uint8_t) prio[i];
2683 }
2684
2685 *p_count = count;
2686
2687 return PJ_SUCCESS;
2688}
2689
2690
2691/*
2692 * Change codec priority.
2693 */
2694PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
2695 pj_uint8_t priority )
2696{
Benny Prijono88accae2008-06-26 15:48:14 +00002697 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002698 pjmedia_codec_mgr *codec_mgr;
2699
2700 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2701
Benny Prijono88accae2008-06-26 15:48:14 +00002702 if (codec_id->slen==1 && *codec_id->ptr=='*')
2703 codec_id = &all;
2704
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002705 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
2706 priority);
2707}
2708
2709
2710/*
2711 * Get codec parameters.
2712 */
2713PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
2714 pjmedia_codec_param *param )
2715{
Benny Prijono88accae2008-06-26 15:48:14 +00002716 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002717 const pjmedia_codec_info *info;
2718 pjmedia_codec_mgr *codec_mgr;
2719 unsigned count = 1;
2720 pj_status_t status;
2721
2722 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2723
Benny Prijono88accae2008-06-26 15:48:14 +00002724 if (codec_id->slen==1 && *codec_id->ptr=='*')
2725 codec_id = &all;
2726
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002727 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
2728 &count, &info, NULL);
2729 if (status != PJ_SUCCESS)
2730 return status;
2731
2732 if (count != 1)
2733 return PJ_ENOTFOUND;
2734
2735 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
2736 return status;
2737}
2738
2739
2740/*
2741 * Set codec parameters.
2742 */
2743PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
2744 const pjmedia_codec_param *param)
2745{
Benny Prijono00cae612006-07-31 15:19:36 +00002746 PJ_UNUSED_ARG(id);
2747 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002748 PJ_TODO(set_codec_param);
2749 return PJ_SUCCESS;
2750}