blob: 8d2d8a8a960e854f75ea375604c1c7f96dd36ba4 [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
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000175#if PJMEDIA_HAS_L16_CODEC
176 /* Register L16 family codecs, but disable all */
177 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
178 if (status != PJ_SUCCESS) {
179 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
180 status);
181 return status;
182 }
183
184 /* Disable ALL L16 codecs */
185 codec_id = pj_str("L16");
186 pjmedia_codec_mgr_set_codec_priority(
187 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
188 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
189
190#endif /* PJMEDIA_HAS_L16_CODEC */
191
192
193 /* Save additional conference bridge parameters for future
194 * reference.
195 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000196 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000197 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000198 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
199 pjsua_var.mconf_cfg.channel_count *
200 pjsua_var.media_cfg.audio_frame_ptime /
201 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000202
Benny Prijono0498d902006-06-19 14:49:14 +0000203 /* Init options for conference bridge. */
204 opt = PJMEDIA_CONF_NO_DEVICE;
205 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000206 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000207 {
208 opt |= PJMEDIA_CONF_SMALL_FILTER;
209 }
210 else if (pjsua_var.media_cfg.quality < 3) {
211 opt |= PJMEDIA_CONF_USE_LINEAR;
212 }
213
214
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000215 /* Init conference bridge. */
216 status = pjmedia_conf_create(pjsua_var.pool,
217 pjsua_var.media_cfg.max_media_ports,
218 pjsua_var.media_cfg.clock_rate,
219 pjsua_var.mconf_cfg.channel_count,
220 pjsua_var.mconf_cfg.samples_per_frame,
221 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000222 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000223 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000224 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000225 status);
226 return status;
227 }
228
229 /* Create null port just in case user wants to use null sound. */
230 status = pjmedia_null_port_create(pjsua_var.pool,
231 pjsua_var.media_cfg.clock_rate,
232 pjsua_var.mconf_cfg.channel_count,
233 pjsua_var.mconf_cfg.samples_per_frame,
234 pjsua_var.mconf_cfg.bits_per_sample,
235 &pjsua_var.null_port);
236 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
237
Benny Prijono6ba8c542007-10-16 01:34:14 +0000238 /* Perform NAT detection */
239 pjsua_detect_nat_type();
240
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000241 return PJ_SUCCESS;
242}
243
244
245/*
246 * Create RTP and RTCP socket pair, and possibly resolve their public
247 * address via STUN.
248 */
249static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
250 pjmedia_sock_info *skinfo)
251{
252 enum {
253 RTP_RETRY = 100
254 };
255 int i;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000256 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000257 pj_sockaddr_in mapped_addr[2];
258 pj_status_t status = PJ_SUCCESS;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000259 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000260 pj_sock_t sock[2];
261
Benny Prijonoc97608e2007-03-23 16:34:20 +0000262 /* Make sure STUN server resolution has completed */
263 status = pjsua_resolve_stun_server(PJ_TRUE);
264 if (status != PJ_SUCCESS) {
265 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
266 return status;
267 }
268
Benny Prijonode479562007-03-15 10:23:55 +0000269 if (next_rtp_port == 0)
270 next_rtp_port = (pj_uint16_t)cfg->port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000271
272 for (i=0; i<2; ++i)
273 sock[i] = PJ_INVALID_SOCKET;
274
Benny Prijono0a5cad82006-09-26 13:21:02 +0000275 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
276 if (cfg->bound_addr.slen) {
277 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
278 if (status != PJ_SUCCESS) {
279 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
280 status);
281 return status;
282 }
283 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000284
285 /* Loop retry to bind RTP and RTCP sockets. */
Benny Prijonode479562007-03-15 10:23:55 +0000286 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000287
288 /* Create and bind RTP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000289 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000290 if (status != PJ_SUCCESS) {
291 pjsua_perror(THIS_FILE, "socket() error", status);
292 return status;
293 }
294
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000295 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
296 next_rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000297 if (status != PJ_SUCCESS) {
298 pj_sock_close(sock[0]);
299 sock[0] = PJ_INVALID_SOCKET;
300 continue;
301 }
302
303 /* Create and bind RTCP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000304 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000305 if (status != PJ_SUCCESS) {
306 pjsua_perror(THIS_FILE, "socket() error", status);
307 pj_sock_close(sock[0]);
308 return status;
309 }
310
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000311 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
312 (pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000313 if (status != PJ_SUCCESS) {
314 pj_sock_close(sock[0]);
315 sock[0] = PJ_INVALID_SOCKET;
316
317 pj_sock_close(sock[1]);
318 sock[1] = PJ_INVALID_SOCKET;
319 continue;
320 }
321
322 /*
323 * If we're configured to use STUN, then find out the mapped address,
324 * and make sure that the mapped RTCP port is adjacent with the RTP.
325 */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000326 if (pjsua_var.stun_srv.addr.sa_family != 0) {
327 char ip_addr[32];
328 pj_str_t stun_srv;
329
330 pj_ansi_strcpy(ip_addr,
331 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
332 stun_srv = pj_str(ip_addr);
333
Benny Prijono14c2b862007-02-21 00:40:05 +0000334 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000335 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
336 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000337 mapped_addr);
338 if (status != PJ_SUCCESS) {
339 pjsua_perror(THIS_FILE, "STUN resolve error", status);
340 goto on_error;
341 }
342
Benny Prijono80eee892007-11-03 22:43:23 +0000343#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000344 if (pj_ntohs(mapped_addr[1].sin_port) ==
345 pj_ntohs(mapped_addr[0].sin_port)+1)
346 {
347 /* Success! */
348 break;
349 }
350
351 pj_sock_close(sock[0]);
352 sock[0] = PJ_INVALID_SOCKET;
353
354 pj_sock_close(sock[1]);
355 sock[1] = PJ_INVALID_SOCKET;
Benny Prijono80eee892007-11-03 22:43:23 +0000356#else
357 if (pj_ntohs(mapped_addr[1].sin_port) !=
358 pj_ntohs(mapped_addr[0].sin_port)+1)
359 {
360 PJ_LOG(4,(THIS_FILE,
361 "Note: STUN mapped RTCP port %d is not adjacent"
362 " to RTP port %d",
363 pj_ntohs(mapped_addr[1].sin_port),
364 pj_ntohs(mapped_addr[0].sin_port)));
365 }
366 /* Success! */
367 break;
368#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000369
Benny Prijono0a5cad82006-09-26 13:21:02 +0000370 } else if (cfg->public_addr.slen) {
371
372 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000373 (pj_uint16_t)next_rtp_port);
Benny Prijono0a5cad82006-09-26 13:21:02 +0000374 if (status != PJ_SUCCESS)
375 goto on_error;
376
377 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000378 (pj_uint16_t)(next_rtp_port+1));
Benny Prijono0a5cad82006-09-26 13:21:02 +0000379 if (status != PJ_SUCCESS)
380 goto on_error;
381
382 break;
383
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000384 } else {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000385
Benny Prijono42d08d22007-12-20 11:23:07 +0000386 if (bound_addr.sin_addr.s_addr == 0) {
387 pj_sockaddr addr;
388
389 /* Get local IP address. */
390 status = pj_gethostip(pj_AF_INET(), &addr);
391 if (status != PJ_SUCCESS)
392 goto on_error;
393
394 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
395 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000396
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000397 for (i=0; i<2; ++i) {
398 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
Benny Prijono42d08d22007-12-20 11:23:07 +0000399 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000400 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000401
Benny Prijonode479562007-03-15 10:23:55 +0000402 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
403 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000404 break;
405 }
406 }
407
408 if (sock[0] == PJ_INVALID_SOCKET) {
409 PJ_LOG(1,(THIS_FILE,
410 "Unable to find appropriate RTP/RTCP ports combination"));
411 goto on_error;
412 }
413
414
415 skinfo->rtp_sock = sock[0];
416 pj_memcpy(&skinfo->rtp_addr_name,
417 &mapped_addr[0], sizeof(pj_sockaddr_in));
418
419 skinfo->rtcp_sock = sock[1];
420 pj_memcpy(&skinfo->rtcp_addr_name,
421 &mapped_addr[1], sizeof(pj_sockaddr_in));
422
Benny Prijono8b22ce12008-02-08 12:57:55 +0000423 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000424 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
425 sizeof(addr_buf), 3)));
Benny Prijono8b22ce12008-02-08 12:57:55 +0000426 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000427 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
428 sizeof(addr_buf), 3)));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000429
Benny Prijonode479562007-03-15 10:23:55 +0000430 next_rtp_port += 2;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000431 return PJ_SUCCESS;
432
433on_error:
434 for (i=0; i<2; ++i) {
435 if (sock[i] != PJ_INVALID_SOCKET)
436 pj_sock_close(sock[i]);
437 }
438 return status;
439}
440
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000441/* Check if sound device is idle. */
442static void check_snd_dev_idle()
443{
444
445 /* Activate sound device auto-close timer if sound device is idle.
446 * It is idle when there is no port connection in the bridge.
447 */
448 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) &&
449 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
450 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
451 pjsua_var.media_cfg.snd_auto_close_time >= 0)
452 {
453 pj_time_val delay;
454
455 delay.msec = 0;
456 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
457
458 pjsua_var.snd_idle_timer.id = PJ_TRUE;
459 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
460 &delay);
461 }
462}
463
464
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000465/* Timer callback to close sound device */
466static void close_snd_timer_cb( pj_timer_heap_t *th,
467 pj_timer_entry *entry)
468{
469 PJ_UNUSED_ARG(th);
470
471 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
472 pjsua_var.media_cfg.snd_auto_close_time));
473
474 entry->id = PJ_FALSE;
475
476 close_snd_dev();
477}
478
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000479
480/*
481 * Start pjsua media subsystem.
482 */
483pj_status_t pjsua_media_subsys_start(void)
484{
485 pj_status_t status;
486
487 /* Create media for calls, if none is specified */
488 if (pjsua_var.calls[0].med_tp == NULL) {
489 pjsua_transport_config transport_cfg;
490
491 /* Create default transport config */
492 pjsua_transport_config_default(&transport_cfg);
493 transport_cfg.port = DEFAULT_RTP_PORT;
494
495 status = pjsua_media_transports_create(&transport_cfg);
496 if (status != PJ_SUCCESS)
497 return status;
498 }
499
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000500 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
501 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000502
503 return PJ_SUCCESS;
504}
505
506
507/*
508 * Destroy pjsua media subsystem.
509 */
510pj_status_t pjsua_media_subsys_destroy(void)
511{
512 unsigned i;
513
514 close_snd_dev();
515
516 if (pjsua_var.mconf) {
517 pjmedia_conf_destroy(pjsua_var.mconf);
518 pjsua_var.mconf = NULL;
519 }
520
521 if (pjsua_var.null_port) {
522 pjmedia_port_destroy(pjsua_var.null_port);
523 pjsua_var.null_port = NULL;
524 }
525
526 /* Destroy file players */
527 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
528 if (pjsua_var.player[i].port) {
529 pjmedia_port_destroy(pjsua_var.player[i].port);
530 pjsua_var.player[i].port = NULL;
531 }
532 }
533
534 /* Destroy file recorders */
535 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
536 if (pjsua_var.recorder[i].port) {
537 pjmedia_port_destroy(pjsua_var.recorder[i].port);
538 pjsua_var.recorder[i].port = NULL;
539 }
540 }
541
542 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000543 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono311b63f2008-07-14 11:31:40 +0000544 if (pjsua_var.calls[i].med_tp_st != PJSUA_MED_TP_IDLE) {
545 pjsua_media_channel_deinit(i);
546 }
Benny Prijono40860c32008-09-04 13:55:33 +0000547 if (pjsua_var.calls[i].med_tp && pjsua_var.calls[i].med_tp_auto_del) {
548 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000549 }
Benny Prijono40860c32008-09-04 13:55:33 +0000550 pjsua_var.calls[i].med_tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000551 }
552
553 /* Destroy media endpoint. */
554 if (pjsua_var.med_endpt) {
555
556 /* Shutdown all codecs: */
557# if PJMEDIA_HAS_SPEEX_CODEC
558 pjmedia_codec_speex_deinit();
559# endif /* PJMEDIA_HAS_SPEEX_CODEC */
560
561# if PJMEDIA_HAS_GSM_CODEC
562 pjmedia_codec_gsm_deinit();
563# endif /* PJMEDIA_HAS_GSM_CODEC */
564
565# if PJMEDIA_HAS_G711_CODEC
566 pjmedia_codec_g711_deinit();
567# endif /* PJMEDIA_HAS_G711_CODEC */
568
Benny Prijono7ffd7752008-03-17 14:07:53 +0000569# if PJMEDIA_HAS_G722_CODEC
570 pjmedia_codec_g722_deinit();
571# endif /* PJMEDIA_HAS_G722_CODEC */
572
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000573# if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000574 pjmedia_codec_ipp_deinit();
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000575# endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000576
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000577# if PJMEDIA_HAS_L16_CODEC
578 pjmedia_codec_l16_deinit();
579# endif /* PJMEDIA_HAS_L16_CODEC */
580
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000581 pjmedia_endpt_destroy(pjsua_var.med_endpt);
582 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000583
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000584 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000585 // Not necessary, as pjmedia_snd_deinit() should have been called
586 // in pjmedia_endpt_destroy().
587 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000588 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000589
Benny Prijonode479562007-03-15 10:23:55 +0000590 /* Reset RTP port */
591 next_rtp_port = 0;
592
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000593 return PJ_SUCCESS;
594}
595
596
Benny Prijonoc97608e2007-03-23 16:34:20 +0000597/* Create normal UDP media transports */
598static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000599{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000600 unsigned i;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000601 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000602 pj_status_t status;
603
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000604 /* Create each media transport */
605 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
606
Benny Prijono617c5bc2007-04-02 19:51:21 +0000607 status = create_rtp_rtcp_sock(cfg, &skinfo);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000608 if (status != PJ_SUCCESS) {
609 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
610 status);
611 goto on_error;
612 }
Benny Prijonod8179652008-01-23 20:39:07 +0000613
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000614 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000615 &skinfo, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000616 &pjsua_var.calls[i].med_tp);
617 if (status != PJ_SUCCESS) {
618 pjsua_perror(THIS_FILE, "Unable to create media transport",
619 status);
620 goto on_error;
621 }
Benny Prijono00cae612006-07-31 15:19:36 +0000622
Benny Prijonod8179652008-01-23 20:39:07 +0000623 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
624 PJMEDIA_DIR_ENCODING,
625 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000626
Benny Prijonod8179652008-01-23 20:39:07 +0000627 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
628 PJMEDIA_DIR_DECODING,
629 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000630
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000631 }
632
Benny Prijonoc97608e2007-03-23 16:34:20 +0000633 return PJ_SUCCESS;
634
635on_error:
636 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
637 if (pjsua_var.calls[i].med_tp != NULL) {
638 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
639 pjsua_var.calls[i].med_tp = NULL;
640 }
641 }
642
643 return status;
644}
645
646
Benny Prijono096c56c2007-09-15 08:30:16 +0000647/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000648static void on_ice_complete(pjmedia_transport *tp,
649 pj_ice_strans_op op,
650 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000651{
Benny Prijonof76e1392008-06-06 14:51:48 +0000652 unsigned id;
Benny Prijono096c56c2007-09-15 08:30:16 +0000653 pj_bool_t found = PJ_FALSE;
654
Benny Prijono096c56c2007-09-15 08:30:16 +0000655 /* Find call which has this media transport */
656
657 PJSUA_LOCK();
658
Benny Prijonof76e1392008-06-06 14:51:48 +0000659 for (id=0; id<pjsua_var.ua_cfg.max_calls; ++id) {
660 if (pjsua_var.calls[id].med_tp == tp ||
661 pjsua_var.calls[id].med_orig == tp)
662 {
663 found = PJ_TRUE;
664 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000665 }
666 }
667
668 PJSUA_UNLOCK();
669
Benny Prijonof76e1392008-06-06 14:51:48 +0000670 if (!found)
671 return;
672
673 switch (op) {
674 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono224b4e22008-06-19 14:10:28 +0000675 pjsua_var.calls[id].med_tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000676 break;
677 case PJ_ICE_STRANS_OP_NEGOTIATION:
678 if (result != PJ_SUCCESS) {
679 pjsua_var.calls[id].media_st = PJSUA_CALL_MEDIA_ERROR;
680 pjsua_var.calls[id].media_dir = PJMEDIA_DIR_NONE;
681
682 if (pjsua_var.ua_cfg.cb.on_call_media_state) {
683 pjsua_var.ua_cfg.cb.on_call_media_state(id);
684 }
685 }
686 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000687 }
688}
689
690
Benny Prijonof76e1392008-06-06 14:51:48 +0000691/* Parse "HOST:PORT" format */
692static pj_status_t parse_host_port(const pj_str_t *host_port,
693 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000694{
Benny Prijonof76e1392008-06-06 14:51:48 +0000695 pj_str_t str_port;
696
697 str_port.ptr = pj_strchr(host_port, ':');
698 if (str_port.ptr != NULL) {
699 int iport;
700
701 host->ptr = host_port->ptr;
702 host->slen = (str_port.ptr - host->ptr);
703 str_port.ptr++;
704 str_port.slen = host_port->slen - host->slen - 1;
705 iport = (int)pj_strtoul(&str_port);
706 if (iport < 1 || iport > 65535)
707 return PJ_EINVAL;
708 *port = (pj_uint16_t)iport;
709 } else {
710 *host = *host_port;
711 *port = 0;
712 }
713
714 return PJ_SUCCESS;
715}
716
717/* Create ICE media transports (when ice is enabled) */
718static pj_status_t create_ice_media_transports(void)
719{
720 char stunip[PJ_INET6_ADDRSTRLEN];
721 pj_ice_strans_cfg ice_cfg;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000722 unsigned i;
723 pj_status_t status;
724
Benny Prijonoda9785b2007-04-02 20:43:06 +0000725 /* Make sure STUN server resolution has completed */
726 status = pjsua_resolve_stun_server(PJ_TRUE);
727 if (status != PJ_SUCCESS) {
728 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
729 return status;
730 }
731
Benny Prijonof76e1392008-06-06 14:51:48 +0000732 /* Create ICE stream transport configuration */
733 pj_ice_strans_cfg_default(&ice_cfg);
734 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
735 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
736 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
737
738 ice_cfg.af = pj_AF_INET();
739 ice_cfg.resolver = pjsua_var.resolver;
740
741 /* Configure STUN settings */
742 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
743 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
744 ice_cfg.stun.server = pj_str(stunip);
745 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
746 }
747 ice_cfg.stun.no_host_cands = pjsua_var.media_cfg.ice_no_host_cands;
748
749 /* Configure TURN settings */
750 if (pjsua_var.media_cfg.enable_turn) {
751 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
752 &ice_cfg.turn.server,
753 &ice_cfg.turn.port);
754 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
755 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
756 return PJ_EINVAL;
757 }
758 if (ice_cfg.turn.port == 0)
759 ice_cfg.turn.port = 3479;
760 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
761 pj_memcpy(&ice_cfg.turn.auth_cred,
762 &pjsua_var.media_cfg.turn_auth_cred,
763 sizeof(ice_cfg.turn.auth_cred));
764 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000765
Benny Prijonoc97608e2007-03-23 16:34:20 +0000766 /* Create each media transport */
767 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono096c56c2007-09-15 08:30:16 +0000768 pjmedia_ice_cb ice_cb;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000769 char name[32];
Benny Prijonof76e1392008-06-06 14:51:48 +0000770 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000771
Benny Prijono096c56c2007-09-15 08:30:16 +0000772 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
773 ice_cb.on_ice_complete = &on_ice_complete;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000774 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i);
Benny Prijono224b4e22008-06-19 14:10:28 +0000775 pjsua_var.calls[i].med_tp_ready = PJ_EPENDING;
Benny Prijonof76e1392008-06-06 14:51:48 +0000776
777 comp_cnt = 1;
Benny Prijono551af422008-08-07 09:55:52 +0000778 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
Benny Prijonof76e1392008-06-06 14:51:48 +0000779 ++comp_cnt;
780
781 status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
782 &ice_cfg, &ice_cb,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000783 &pjsua_var.calls[i].med_tp);
784 if (status != PJ_SUCCESS) {
785 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
786 status);
787 goto on_error;
788 }
789
Benny Prijonof76e1392008-06-06 14:51:48 +0000790 /* Wait until transport is initialized, or time out */
791 PJSUA_UNLOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000792 while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000793 pjsua_handle_events(100);
794 }
795 PJSUA_LOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000796 if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000797 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
Benny Prijono224b4e22008-06-19 14:10:28 +0000798 pjsua_var.calls[i].med_tp_ready);
799 status = pjsua_var.calls[i].med_tp_ready;
Benny Prijonof76e1392008-06-06 14:51:48 +0000800 goto on_error;
801 }
802
Benny Prijonod8179652008-01-23 20:39:07 +0000803 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
804 PJMEDIA_DIR_ENCODING,
805 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono11da9bc2007-09-15 08:55:00 +0000806
Benny Prijonod8179652008-01-23 20:39:07 +0000807 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
808 PJMEDIA_DIR_DECODING,
809 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000810 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000811
812 return PJ_SUCCESS;
813
814on_error:
815 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
816 if (pjsua_var.calls[i].med_tp != NULL) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000817 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000818 pjsua_var.calls[i].med_tp = NULL;
819 }
820 }
821
Benny Prijonoc97608e2007-03-23 16:34:20 +0000822 return status;
823}
824
825
826/*
827 * Create UDP media transports for all the calls. This function creates
828 * one UDP media transport for each call.
829 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +0000830PJ_DEF(pj_status_t) pjsua_media_transports_create(
831 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000832{
833 pjsua_transport_config cfg;
834 unsigned i;
835 pj_status_t status;
836
837
838 /* Make sure pjsua_init() has been called */
839 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
840
841 PJSUA_LOCK();
842
843 /* Delete existing media transports */
844 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono40860c32008-09-04 13:55:33 +0000845 if (pjsua_var.calls[i].med_tp != NULL &&
846 pjsua_var.calls[i].med_tp_auto_del)
847 {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000848 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
849 pjsua_var.calls[i].med_tp = NULL;
Nanang Izzuddind704a8b2008-09-23 16:34:07 +0000850 pjsua_var.calls[i].med_orig = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000851 }
852 }
853
854 /* Copy config */
855 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
856
Benny Prijono40860c32008-09-04 13:55:33 +0000857 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000858 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000859 status = create_ice_media_transports();
Benny Prijonoc97608e2007-03-23 16:34:20 +0000860 } else {
861 status = create_udp_media_transports(&cfg);
862 }
863
Benny Prijono40860c32008-09-04 13:55:33 +0000864 /* Set media transport auto_delete to True */
865 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
866 pjsua_var.calls[i].med_tp_auto_del = PJ_TRUE;
867 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000868
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000869 PJSUA_UNLOCK();
870
871 return status;
872}
873
Benny Prijono40860c32008-09-04 13:55:33 +0000874/*
875 * Attach application's created media transports.
876 */
877PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
878 unsigned count,
879 pj_bool_t auto_delete)
880{
881 unsigned i;
882
883 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
884
885 /* Assign the media transports */
886 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
887 if (pjsua_var.calls[i].med_tp != NULL &&
888 pjsua_var.calls[i].med_tp_auto_del)
889 {
890 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
891 }
892
893 pjsua_var.calls[i].med_tp = tp[i].transport;
894 pjsua_var.calls[i].med_tp_auto_del = auto_delete;
895 }
896
897 return PJ_SUCCESS;
898}
899
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000900
Benny Prijonoc97608e2007-03-23 16:34:20 +0000901pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
Benny Prijonod8179652008-01-23 20:39:07 +0000902 pjsip_role_e role,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000903 int security_level,
Benny Prijono224b4e22008-06-19 14:10:28 +0000904 pj_pool_t *tmp_pool,
905 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000906 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000907{
908 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono224b4e22008-06-19 14:10:28 +0000909 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000910
Benny Prijonod8179652008-01-23 20:39:07 +0000911#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
912 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
913 pjmedia_srtp_setting srtp_opt;
Benny Prijonoa310bd22008-06-27 21:19:44 +0000914 pjmedia_transport *srtp = NULL;
Benny Prijonod8179652008-01-23 20:39:07 +0000915#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000916
Benny Prijonod8179652008-01-23 20:39:07 +0000917 PJ_UNUSED_ARG(role);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000918
Benny Prijonod8179652008-01-23 20:39:07 +0000919 /* Return error if media transport has not been created yet
920 * (e.g. application is starting)
921 */
922 if (call->med_tp == NULL) {
Benny Prijono03789052008-09-16 14:30:50 +0000923 if (sip_err_code)
924 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
Benny Prijonod8179652008-01-23 20:39:07 +0000925 return PJ_EBUSY;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000926 }
927
Benny Prijonod8179652008-01-23 20:39:07 +0000928#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Benny Prijono53a7c702008-04-14 02:57:29 +0000929 /* This function may be called when SRTP transport already exists
930 * (e.g: in re-invite, update), don't need to destroy/re-create.
931 */
932 if (!call->med_orig || call->med_tp == call->med_orig) {
933
934 /* Check if SRTP requires secure signaling */
935 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
936 if (security_level < acc->cfg.srtp_secure_signaling) {
937 if (sip_err_code)
938 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
939 return PJSIP_ESESSIONINSECURE;
940 }
Benny Prijonod8179652008-01-23 20:39:07 +0000941 }
Benny Prijonod8179652008-01-23 20:39:07 +0000942
Benny Prijono53a7c702008-04-14 02:57:29 +0000943 /* Always create SRTP adapter */
944 pjmedia_srtp_setting_default(&srtp_opt);
945 srtp_opt.close_member_tp = PJ_FALSE;
Nanang Izzuddin4375f902008-06-26 19:12:09 +0000946 /* If media session has been ever established, let's use remote's
947 * preference in SRTP usage policy, especially when it is stricter.
948 */
949 if (call->rem_srtp_use > acc->cfg.use_srtp)
950 srtp_opt.use = call->rem_srtp_use;
951 else
952 srtp_opt.use = acc->cfg.use_srtp;
953
Benny Prijono53a7c702008-04-14 02:57:29 +0000954 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
955 call->med_tp,
956 &srtp_opt, &srtp);
957 if (status != PJ_SUCCESS) {
958 if (sip_err_code)
959 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
960 return status;
961 }
Benny Prijonod8179652008-01-23 20:39:07 +0000962
Benny Prijono53a7c702008-04-14 02:57:29 +0000963 /* Set SRTP as current media transport */
964 call->med_orig = call->med_tp;
965 call->med_tp = srtp;
966 }
Benny Prijonod8179652008-01-23 20:39:07 +0000967#else
968 call->med_orig = call->med_tp;
969 PJ_UNUSED_ARG(security_level);
970#endif
971
Benny Prijonoa310bd22008-06-27 21:19:44 +0000972 /* Find out which media line in SDP that we support. If we are offerer,
973 * audio will be at index 0 in SDP.
974 */
975 if (rem_sdp == 0) {
976 call->audio_idx = 0;
977 }
978 /* Otherwise find out the candidate audio media line in SDP */
979 else {
980 unsigned i;
981 pj_bool_t srtp_active;
982
983#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
984 srtp_active = acc->cfg.use_srtp && srtp != NULL;
985#else
986 srtp_active = PJ_FALSE;
987#endif
988
989 /* Media count must have been checked */
990 pj_assert(rem_sdp->media_count != 0);
991
992 for (i=0; i<rem_sdp->media_count; ++i) {
993 const pjmedia_sdp_media *m = rem_sdp->media[i];
994
995 /* Skip if media is not audio */
996 if (pj_stricmp2(&m->desc.media, "audio") != 0)
997 continue;
998
999 /* Skip if media is disabled */
1000 if (m->desc.port == 0)
1001 continue;
1002
1003 /* Skip if transport is not supported */
1004 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") != 0 &&
1005 pj_stricmp2(&m->desc.transport, "RTP/SAVP") != 0)
1006 {
1007 continue;
1008 }
1009
1010 if (call->audio_idx == -1) {
1011 call->audio_idx = i;
1012 } else {
1013 /* We've found multiple candidates. This could happen
1014 * e.g. when remote is offering both RTP/AVP and RTP/AVP,
1015 * or when remote for some reason offers two audio.
1016 */
1017
1018 if (srtp_active &&
1019 pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0)
1020 {
1021 /* Prefer RTP/SAVP when our media transport is SRTP */
1022 call->audio_idx = i;
1023 } else if (!srtp_active &&
1024 pj_stricmp2(&m->desc.transport, "RTP/AVP")==0)
1025 {
1026 /* Prefer RTP/AVP when our media transport is NOT SRTP */
1027 call->audio_idx = i;
1028 }
1029 }
1030 }
1031 }
1032
1033 /* Reject offer if we couldn't find a good m=audio line in offer */
1034 if (call->audio_idx < 0) {
Benny Prijonoab8dba92008-06-27 21:59:15 +00001035 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001036 pjsua_media_channel_deinit(call_id);
Benny Prijonoab8dba92008-06-27 21:59:15 +00001037 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001038 }
1039
1040 PJ_LOG(4,(THIS_FILE, "Media index %d selected for call %d",
1041 call->audio_idx, call->index));
1042
Benny Prijono224b4e22008-06-19 14:10:28 +00001043 /* Create the media transport */
1044 status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001045 rem_sdp, call->audio_idx);
Benny Prijono224b4e22008-06-19 14:10:28 +00001046 if (status != PJ_SUCCESS) {
1047 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1048 pjsua_media_channel_deinit(call_id);
1049 return status;
1050 }
1051
1052 call->med_tp_st = PJSUA_MED_TP_INIT;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001053 return PJ_SUCCESS;
1054}
1055
1056pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1057 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001058 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001059 pjmedia_sdp_session **p_sdp,
1060 int *sip_status_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001061{
Benny Prijonoa310bd22008-06-27 21:19:44 +00001062 enum { MAX_MEDIA = 1 };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001063 pjmedia_sdp_session *sdp;
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001064 pjmedia_transport_info tpinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001065 pjsua_call *call = &pjsua_var.calls[call_id];
1066 pj_status_t status;
1067
Benny Prijono55e82352007-05-10 20:49:08 +00001068 /* Return error if media transport has not been created yet
1069 * (e.g. application is starting)
1070 */
1071 if (call->med_tp == NULL) {
1072 return PJ_EBUSY;
1073 }
1074
Benny Prijonoa310bd22008-06-27 21:19:44 +00001075 /* Media index must have been determined before */
1076 pj_assert(call->audio_idx != -1);
1077
Benny Prijono224b4e22008-06-19 14:10:28 +00001078 /* Create media if it's not created. This could happen when call is
1079 * currently on-hold
1080 */
1081 if (call->med_tp_st == PJSUA_MED_TP_IDLE) {
1082 pjsip_role_e role;
1083 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1084 status = pjsua_media_channel_init(call_id, role, call->secure_level,
1085 pool, rem_sdp, sip_status_code);
1086 if (status != PJ_SUCCESS)
1087 return status;
1088 }
1089
Benny Prijono617c5bc2007-04-02 19:51:21 +00001090 /* Get media socket info */
Benny Prijono734fc2d2008-03-17 16:05:35 +00001091 pjmedia_transport_info_init(&tpinfo);
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001092 pjmedia_transport_get_info(call->med_tp, &tpinfo);
Benny Prijono617c5bc2007-04-02 19:51:21 +00001093
1094 /* Create SDP */
Benny Prijonod8179652008-01-23 20:39:07 +00001095 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA,
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001096 &tpinfo.sock_info, &sdp);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001097 if (status != PJ_SUCCESS) {
1098 if (sip_status_code) *sip_status_code = 500;
Benny Prijono224b4e22008-06-19 14:10:28 +00001099 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001100 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001101
Benny Prijonoa310bd22008-06-27 21:19:44 +00001102 /* If we're answering and the selected media is not the first media
1103 * in SDP, then fill in the unselected media with with zero port.
1104 * Otherwise we'll crash in transport_encode_sdp() because the media
1105 * lines are not aligned between offer and answer.
1106 */
1107 if (rem_sdp && call->audio_idx != 0) {
1108 unsigned i;
1109
1110 for (i=0; i<rem_sdp->media_count; ++i) {
1111 const pjmedia_sdp_media *rem_m = rem_sdp->media[i];
1112 pjmedia_sdp_media *m;
1113 const pjmedia_sdp_attr *a;
1114
1115 if ((int)i == call->audio_idx)
1116 continue;
1117
1118 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1119 pj_strdup(pool, &m->desc.media, &rem_m->desc.media);
1120 pj_strdup(pool, &m->desc.transport, &rem_m->desc.transport);
1121 m->desc.port = 0;
1122
1123 /* Add one format, copy from the offer. And copy the corresponding
1124 * rtpmap and fmtp attributes too.
1125 */
1126 m->desc.fmt_count = 1;
1127 pj_strdup(pool, &m->desc.fmt[0], &rem_m->desc.fmt[0]);
1128 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1129 "rtpmap", &m->desc.fmt[0])) != NULL)
1130 {
1131 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1132 }
1133 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1134 "fmtp", &m->desc.fmt[0])) != NULL)
1135 {
1136 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1137 }
1138
1139 if (i==sdp->media_count)
1140 sdp->media[sdp->media_count++] = m;
1141 else {
1142 pj_array_insert(sdp->media, sizeof(sdp->media[0]),
1143 sdp->media_count, i, &m);
1144 ++sdp->media_count;
1145 }
1146 }
1147 }
1148
Benny Prijono6ba8c542007-10-16 01:34:14 +00001149 /* Add NAT info in the SDP */
1150 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1151 pjmedia_sdp_attr *a;
1152 pj_str_t value;
1153 char nat_info[80];
1154
1155 value.ptr = nat_info;
1156 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1157 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1158 "%d", pjsua_var.nat_type);
1159 } else {
1160 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1161 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1162 "%d %s",
1163 pjsua_var.nat_type,
1164 type_name);
1165 }
1166
1167 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1168
1169 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1170
1171 }
1172
Benny Prijonod8179652008-01-23 20:39:07 +00001173 /* Give the SDP to media transport */
Benny Prijono224b4e22008-06-19 14:10:28 +00001174 status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001175 call->audio_idx);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001176 if (status != PJ_SUCCESS) {
1177 if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono224b4e22008-06-19 14:10:28 +00001178 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001179 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001180
1181 *p_sdp = sdp;
1182 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001183}
1184
1185
1186static void stop_media_session(pjsua_call_id call_id)
1187{
1188 pjsua_call *call = &pjsua_var.calls[call_id];
1189
1190 if (call->conf_slot != PJSUA_INVALID_ID) {
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001191 if (pjsua_var.mconf) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001192 pjsua_conf_remove_port(call->conf_slot);
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001193 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001194 call->conf_slot = PJSUA_INVALID_ID;
1195 }
1196
1197 if (call->session) {
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001198 pjmedia_rtcp_stat stat;
1199
Nanang Izzuddin437d77c2008-08-26 18:04:15 +00001200 if (pjmedia_session_get_stream_stat(call->session, 0, &stat)
1201 == PJ_SUCCESS)
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001202 {
1203 /* Save RTP timestamp & sequence, so when media session is
1204 * restarted, those values will be restored as the initial
1205 * RTP timestamp & sequence of the new media session. So in
1206 * the same call session, RTP timestamp and sequence are
1207 * guaranteed to be contigue.
1208 */
1209 call->rtp_tx_seq_ts_set = 1 | (1 << 1);
1210 call->rtp_tx_seq = stat.rtp_tx_last_seq;
1211 call->rtp_tx_ts = stat.rtp_tx_last_ts;
1212 }
1213
Benny Prijonofc13bf62008-02-20 08:56:15 +00001214 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1215 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0);
1216 }
1217
Benny Prijonoc97608e2007-03-23 16:34:20 +00001218 pjmedia_session_destroy(call->session);
1219 call->session = NULL;
1220
1221 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
1222 call_id));
1223
1224 }
1225
1226 call->media_st = PJSUA_CALL_MEDIA_NONE;
1227}
1228
1229pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1230{
1231 pjsua_call *call = &pjsua_var.calls[call_id];
1232
1233 stop_media_session(call_id);
1234
Benny Prijono224b4e22008-06-19 14:10:28 +00001235 if (call->med_tp_st != PJSUA_MED_TP_IDLE) {
1236 pjmedia_transport_media_stop(call->med_tp);
1237 call->med_tp_st = PJSUA_MED_TP_IDLE;
1238 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001239
Benny Prijono311b63f2008-07-14 11:31:40 +00001240 if (call->med_orig && call->med_tp && call->med_tp != call->med_orig) {
Benny Prijonod8179652008-01-23 20:39:07 +00001241 pjmedia_transport_close(call->med_tp);
1242 call->med_tp = call->med_orig;
1243 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00001244
1245 check_snd_dev_idle();
1246
Benny Prijonoc97608e2007-03-23 16:34:20 +00001247 return PJ_SUCCESS;
1248}
1249
1250
1251/*
1252 * DTMF callback from the stream.
1253 */
1254static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1255 int digit)
1256{
1257 PJ_UNUSED_ARG(strm);
1258
Benny Prijono0c068262008-02-14 14:38:52 +00001259 /* For discussions about call mutex protection related to this
1260 * callback, please see ticket #460:
1261 * http://trac.pjsip.org/repos/ticket/460#comment:4
1262 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001263 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1264 pjsua_call_id call_id;
1265
Benny Prijonod8179652008-01-23 20:39:07 +00001266 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001267 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1268 }
1269}
1270
1271
1272pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijono224b4e22008-06-19 14:10:28 +00001273 const pjmedia_sdp_session *local_sdp,
Benny Prijonodbce2cf2007-03-28 16:24:00 +00001274 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001275{
1276 int prev_media_st = 0;
1277 pjsua_call *call = &pjsua_var.calls[call_id];
1278 pjmedia_session_info sess_info;
Benny Prijono91e567e2007-12-28 08:51:58 +00001279 pjmedia_stream_info *si = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001280 pjmedia_port *media_port;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001281 pj_status_t status;
1282
1283 /* Destroy existing media session, if any. */
1284 prev_media_st = call->media_st;
1285 stop_media_session(call->index);
1286
1287 /* Create media session info based on SDP parameters.
Benny Prijonoc97608e2007-03-23 16:34:20 +00001288 */
1289 status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
1290 pjsua_var.med_endpt,
Benny Prijono91e567e2007-12-28 08:51:58 +00001291 PJMEDIA_MAX_SDP_MEDIA, &sess_info,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001292 local_sdp, remote_sdp);
1293 if (status != PJ_SUCCESS)
1294 return status;
1295
Benny Prijonoa310bd22008-06-27 21:19:44 +00001296 /* Find which session is audio */
1297 PJ_ASSERT_RETURN(call->audio_idx != -1, PJ_EBUG);
1298 PJ_ASSERT_RETURN(call->audio_idx < (int)sess_info.stream_cnt, PJ_EBUG);
1299 si = &sess_info.stream_info[call->audio_idx];
Benny Prijono91e567e2007-12-28 08:51:58 +00001300
1301 /* Reset session info with only one media stream */
1302 sess_info.stream_cnt = 1;
1303 if (si != &sess_info.stream_info[0])
1304 pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001305
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001306 /* Check if no media is active */
Benny Prijono91e567e2007-12-28 08:51:58 +00001307 if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001308 {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001309 /* Call media state */
1310 call->media_st = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001311
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001312 /* Call media direction */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001313 call->media_dir = PJMEDIA_DIR_NONE;
1314
Benny Prijonod8179652008-01-23 20:39:07 +00001315 /* Shutdown transport's session */
1316 pjmedia_transport_media_stop(call->med_tp);
Benny Prijono224b4e22008-06-19 14:10:28 +00001317 call->med_tp_st = PJSUA_MED_TP_IDLE;
Benny Prijono667952e2007-04-02 19:27:54 +00001318
Benny Prijonoc97608e2007-03-23 16:34:20 +00001319 /* No need because we need keepalive? */
1320
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001321 /* Close upper entry of transport stack */
1322 if (call->med_orig && (call->med_tp != call->med_orig)) {
1323 pjmedia_transport_close(call->med_tp);
1324 call->med_tp = call->med_orig;
1325 }
1326
Benny Prijonoc97608e2007-03-23 16:34:20 +00001327 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001328 pjmedia_transport_info tp_info;
1329
Benny Prijono224b4e22008-06-19 14:10:28 +00001330 /* Start/restart media transport */
Benny Prijonod8179652008-01-23 20:39:07 +00001331 status = pjmedia_transport_media_start(call->med_tp,
1332 call->inv->pool,
1333 local_sdp, remote_sdp, 0);
1334 if (status != PJ_SUCCESS)
1335 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001336
Benny Prijono224b4e22008-06-19 14:10:28 +00001337 call->med_tp_st = PJSUA_MED_TP_RUNNING;
1338
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001339 /* Get remote SRTP usage policy */
1340 pjmedia_transport_info_init(&tp_info);
1341 pjmedia_transport_get_info(call->med_tp, &tp_info);
1342 if (tp_info.specific_info_cnt > 0) {
1343 int i;
1344 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1345 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1346 {
1347 pjmedia_srtp_info *srtp_info =
1348 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1349
1350 call->rem_srtp_use = srtp_info->peer_use;
1351 break;
1352 }
1353 }
1354 }
1355
Benny Prijonoc97608e2007-03-23 16:34:20 +00001356 /* Override ptime, if this option is specified. */
1357 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001358 si->param->setting.frm_per_pkt = (pj_uint8_t)
1359 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1360 if (si->param->setting.frm_per_pkt == 0)
1361 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001362 }
1363
1364 /* Disable VAD, if this option is specified. */
1365 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001366 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001367 }
1368
1369
1370 /* Optionally, application may modify other stream settings here
1371 * (such as jitter buffer parameters, codec ptime, etc.)
1372 */
Benny Prijono91e567e2007-12-28 08:51:58 +00001373 si->jb_init = pjsua_var.media_cfg.jb_init;
1374 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1375 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1376 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001377
Benny Prijono8147f402007-11-21 14:50:07 +00001378 /* Set SSRC */
Benny Prijono91e567e2007-12-28 08:51:58 +00001379 si->ssrc = call->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00001380
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001381 /* Set RTP timestamp & sequence, normally these value are intialized
1382 * automatically when stream session created, but for some cases (e.g:
1383 * call reinvite, call update) timestamp and sequence need to be kept
1384 * contigue.
1385 */
1386 si->rtp_ts = call->rtp_tx_ts;
1387 si->rtp_seq = call->rtp_tx_seq;
1388 si->rtp_seq_ts_set = call->rtp_tx_seq_ts_set;
1389
Benny Prijonoc97608e2007-03-23 16:34:20 +00001390 /* Create session based on session info. */
1391 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
1392 &call->med_tp,
1393 call, &call->session );
1394 if (status != PJ_SUCCESS) {
1395 return status;
1396 }
1397
1398 /* If DTMF callback is installed by application, install our
1399 * callback to the session.
1400 */
1401 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1402 pjmedia_session_set_dtmf_callback(call->session, 0,
1403 &dtmf_callback,
Benny Prijonod8179652008-01-23 20:39:07 +00001404 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001405 }
1406
1407 /* Get the port interface of the first stream in the session.
1408 * We need the port interface to add to the conference bridge.
1409 */
1410 pjmedia_session_get_port(call->session, 0, &media_port);
1411
Benny Prijonofc13bf62008-02-20 08:56:15 +00001412 /* Notify application about stream creation.
1413 * Note: application may modify media_port to point to different
1414 * media port
1415 */
1416 if (pjsua_var.ua_cfg.cb.on_stream_created) {
1417 pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session,
1418 0, &media_port);
1419 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001420
1421 /*
1422 * Add the call to conference bridge.
1423 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001424 {
1425 char tmp[PJSIP_MAX_URL_SIZE];
1426 pj_str_t port_name;
1427
1428 port_name.ptr = tmp;
1429 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1430 call->inv->dlg->remote.info->uri,
1431 tmp, sizeof(tmp));
1432 if (port_name.slen < 1) {
1433 port_name = pj_str("call");
1434 }
1435 status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
1436 media_port,
1437 &port_name,
1438 (unsigned*)&call->conf_slot);
1439 if (status != PJ_SUCCESS) {
1440 return status;
1441 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001442 }
1443
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001444 /* Call media direction */
Benny Prijono91e567e2007-12-28 08:51:58 +00001445 call->media_dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001446
1447 /* Call media state */
1448 if (call->local_hold)
1449 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
1450 else if (call->media_dir == PJMEDIA_DIR_DECODING)
1451 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
1452 else
1453 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001454 }
1455
1456 /* Print info. */
1457 {
1458 char info[80];
1459 int info_len = 0;
1460 unsigned i;
1461
1462 for (i=0; i<sess_info.stream_cnt; ++i) {
1463 int len;
1464 const char *dir;
1465 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1466
1467 switch (strm_info->dir) {
1468 case PJMEDIA_DIR_NONE:
1469 dir = "inactive";
1470 break;
1471 case PJMEDIA_DIR_ENCODING:
1472 dir = "sendonly";
1473 break;
1474 case PJMEDIA_DIR_DECODING:
1475 dir = "recvonly";
1476 break;
1477 case PJMEDIA_DIR_ENCODING_DECODING:
1478 dir = "sendrecv";
1479 break;
1480 default:
1481 dir = "unknown";
1482 break;
1483 }
1484 len = pj_ansi_sprintf( info+info_len,
1485 ", stream #%d: %.*s (%s)", i,
1486 (int)strm_info->fmt.encoding_name.slen,
1487 strm_info->fmt.encoding_name.ptr,
1488 dir);
1489 if (len > 0)
1490 info_len += len;
1491 }
1492 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1493 }
1494
1495 return PJ_SUCCESS;
1496}
1497
1498
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001499/*
1500 * Get maxinum number of conference ports.
1501 */
1502PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1503{
1504 return pjsua_var.media_cfg.max_media_ports;
1505}
1506
1507
1508/*
1509 * Get current number of active ports in the bridge.
1510 */
1511PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1512{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001513 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001514 unsigned count = PJ_ARRAY_SIZE(ports);
1515 pj_status_t status;
1516
1517 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1518 if (status != PJ_SUCCESS)
1519 count = 0;
1520
1521 return count;
1522}
1523
1524
1525/*
1526 * Enumerate all conference ports.
1527 */
1528PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1529 unsigned *count)
1530{
1531 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1532}
1533
1534
1535/*
1536 * Get information about the specified conference port
1537 */
1538PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1539 pjsua_conf_port_info *info)
1540{
1541 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001542 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001543 pj_status_t status;
1544
1545 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1546 if (status != PJ_SUCCESS)
1547 return status;
1548
Benny Prijonoac623b32006-07-03 15:19:31 +00001549 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001550 info->slot_id = id;
1551 info->name = cinfo.name;
1552 info->clock_rate = cinfo.clock_rate;
1553 info->channel_count = cinfo.channel_count;
1554 info->samples_per_frame = cinfo.samples_per_frame;
1555 info->bits_per_sample = cinfo.bits_per_sample;
1556
1557 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001558 info->listener_cnt = cinfo.listener_cnt;
1559 for (i=0; i<cinfo.listener_cnt; ++i) {
1560 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001561 }
1562
1563 return PJ_SUCCESS;
1564}
1565
1566
1567/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001568 * Add arbitrary media port to PJSUA's conference bridge.
1569 */
1570PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1571 pjmedia_port *port,
1572 pjsua_conf_port_id *p_id)
1573{
1574 pj_status_t status;
1575
1576 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1577 port, NULL, (unsigned*)p_id);
1578 if (status != PJ_SUCCESS) {
1579 if (p_id)
1580 *p_id = PJSUA_INVALID_ID;
1581 }
1582
1583 return status;
1584}
1585
1586
1587/*
1588 * Remove arbitrary slot from the conference bridge.
1589 */
1590PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1591{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001592 pj_status_t status;
1593
1594 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1595 check_snd_dev_idle();
1596
1597 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00001598}
1599
1600
1601/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001602 * Establish unidirectional media flow from souce to sink.
1603 */
1604PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1605 pjsua_conf_port_id sink)
1606{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001607 /* If sound device idle timer is active, cancel it first. */
1608 if (pjsua_var.snd_idle_timer.id) {
1609 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
1610 pjsua_var.snd_idle_timer.id = PJ_FALSE;
1611 }
1612
1613 /* Create sound port if none is instantiated */
1614 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1615 !pjsua_var.no_snd)
1616 {
1617 pj_status_t status;
1618
1619 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1620 if (status != PJ_SUCCESS) {
1621 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1622 return status;
1623 }
1624 }
1625
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001626 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
1627}
1628
1629
1630/*
1631 * Disconnect media flow from the source to destination port.
1632 */
1633PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
1634 pjsua_conf_port_id sink)
1635{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001636 pj_status_t status;
1637
1638 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001639 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001640
1641 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001642}
1643
1644
Benny Prijono6dd967c2006-12-26 02:27:14 +00001645/*
1646 * Adjust the signal level to be transmitted from the bridge to the
1647 * specified port by making it louder or quieter.
1648 */
1649PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
1650 float level)
1651{
1652 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
1653 (int)((level-1) * 128));
1654}
1655
1656/*
1657 * Adjust the signal level to be received from the specified port (to
1658 * the bridge) by making it louder or quieter.
1659 */
1660PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
1661 float level)
1662{
1663 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
1664 (int)((level-1) * 128));
1665}
1666
1667
1668/*
1669 * Get last signal level transmitted to or received from the specified port.
1670 */
1671PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
1672 unsigned *tx_level,
1673 unsigned *rx_level)
1674{
1675 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
1676 tx_level, rx_level);
1677}
1678
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001679/*****************************************************************************
1680 * File player.
1681 */
1682
Benny Prijonod5696da2007-07-17 16:25:45 +00001683static char* get_basename(const char *path, unsigned len)
1684{
1685 char *p = ((char*)path) + len;
1686
1687 if (len==0)
1688 return p;
1689
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001690 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00001691
1692 return (p==path) ? p : p+1;
1693}
1694
1695
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001696/*
1697 * Create a file player, and automatically connect this player to
1698 * the conference bridge.
1699 */
1700PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1701 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001702 pjsua_player_id *p_id)
1703{
1704 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001705 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00001706 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001707 pjmedia_port *port;
1708 pj_status_t status;
1709
1710 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1711 return PJ_ETOOMANY;
1712
1713 PJSUA_LOCK();
1714
1715 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1716 if (pjsua_var.player[file_id].port == NULL)
1717 break;
1718 }
1719
1720 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1721 /* This is unexpected */
1722 PJSUA_UNLOCK();
1723 pj_assert(0);
1724 return PJ_EBUG;
1725 }
1726
1727 pj_memcpy(path, filename->ptr, filename->slen);
1728 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00001729
1730 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1731 if (!pool) {
1732 PJSUA_UNLOCK();
1733 return PJ_ENOMEM;
1734 }
1735
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00001736 status = pjmedia_wav_player_port_create(
1737 pool, path,
1738 pjsua_var.mconf_cfg.samples_per_frame *
1739 1000 / pjsua_var.media_cfg.channel_count /
1740 pjsua_var.media_cfg.clock_rate,
1741 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001742 if (status != PJ_SUCCESS) {
1743 PJSUA_UNLOCK();
1744 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001745 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001746 return status;
1747 }
1748
Benny Prijono5297af92008-03-18 13:40:40 +00001749 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001750 port, filename, &slot);
1751 if (status != PJ_SUCCESS) {
1752 pjmedia_port_destroy(port);
1753 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00001754 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1755 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001756 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001757 return status;
1758 }
1759
Benny Prijonoa66c3312007-01-21 23:12:40 +00001760 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00001761 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001762 pjsua_var.player[file_id].port = port;
1763 pjsua_var.player[file_id].slot = slot;
1764
1765 if (p_id) *p_id = file_id;
1766
1767 ++pjsua_var.player_cnt;
1768
1769 PJSUA_UNLOCK();
1770 return PJ_SUCCESS;
1771}
1772
1773
1774/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00001775 * Create a file playlist media port, and automatically add the port
1776 * to the conference bridge.
1777 */
1778PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
1779 unsigned file_count,
1780 const pj_str_t *label,
1781 unsigned options,
1782 pjsua_player_id *p_id)
1783{
1784 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00001785 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001786 pjmedia_port *port;
1787 pj_status_t status;
1788
1789 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1790 return PJ_ETOOMANY;
1791
1792 PJSUA_LOCK();
1793
1794 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1795 if (pjsua_var.player[file_id].port == NULL)
1796 break;
1797 }
1798
1799 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1800 /* This is unexpected */
1801 PJSUA_UNLOCK();
1802 pj_assert(0);
1803 return PJ_EBUG;
1804 }
1805
1806
1807 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
1808 pjsua_var.media_cfg.clock_rate;
1809
Benny Prijonod5696da2007-07-17 16:25:45 +00001810 pool = pjsua_pool_create("playlist", 1000, 1000);
1811 if (!pool) {
1812 PJSUA_UNLOCK();
1813 return PJ_ENOMEM;
1814 }
1815
1816 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001817 file_names, file_count,
1818 ptime, options, 0, &port);
1819 if (status != PJ_SUCCESS) {
1820 PJSUA_UNLOCK();
1821 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001822 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001823 return status;
1824 }
1825
Benny Prijonod5696da2007-07-17 16:25:45 +00001826 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001827 port, &port->info.name, &slot);
1828 if (status != PJ_SUCCESS) {
1829 pjmedia_port_destroy(port);
1830 PJSUA_UNLOCK();
1831 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001832 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001833 return status;
1834 }
1835
1836 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00001837 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001838 pjsua_var.player[file_id].port = port;
1839 pjsua_var.player[file_id].slot = slot;
1840
1841 if (p_id) *p_id = file_id;
1842
1843 ++pjsua_var.player_cnt;
1844
1845 PJSUA_UNLOCK();
1846 return PJ_SUCCESS;
1847
1848}
1849
1850
1851/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001852 * Get conference port ID associated with player.
1853 */
1854PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
1855{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001856 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001857 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1858
1859 return pjsua_var.player[id].slot;
1860}
1861
Benny Prijono469b1522006-12-26 03:05:17 +00001862/*
1863 * Get the media port for the player.
1864 */
Benny Prijonobe41d862008-01-18 13:24:28 +00001865PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00001866 pjmedia_port **p_port)
1867{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001868 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001869 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1870 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1871
1872 *p_port = pjsua_var.player[id].port;
1873
1874 return PJ_SUCCESS;
1875}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001876
1877/*
1878 * Set playback position.
1879 */
1880PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
1881 pj_uint32_t samples)
1882{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001883 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001884 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001885 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001886
1887 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
1888}
1889
1890
1891/*
1892 * Close the file, remove the player from the bridge, and free
1893 * resources associated with the file player.
1894 */
1895PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
1896{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001897 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001898 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1899
1900 PJSUA_LOCK();
1901
1902 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001903 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001904 pjmedia_port_destroy(pjsua_var.player[id].port);
1905 pjsua_var.player[id].port = NULL;
1906 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00001907 pj_pool_release(pjsua_var.player[id].pool);
1908 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001909 pjsua_var.player_cnt--;
1910 }
1911
1912 PJSUA_UNLOCK();
1913
1914 return PJ_SUCCESS;
1915}
1916
1917
1918/*****************************************************************************
1919 * File recorder.
1920 */
1921
1922/*
1923 * Create a file recorder, and automatically connect this recorder to
1924 * the conference bridge.
1925 */
1926PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00001927 unsigned enc_type,
1928 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001929 pj_ssize_t max_size,
1930 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001931 pjsua_recorder_id *p_id)
1932{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001933 enum Format
1934 {
1935 FMT_UNKNOWN,
1936 FMT_WAV,
1937 FMT_MP3,
1938 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001939 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001940 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001941 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00001942 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00001943 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001944 pjmedia_port *port;
1945 pj_status_t status;
1946
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001947 /* Filename must present */
1948 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
1949
Benny Prijono00cae612006-07-31 15:19:36 +00001950 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001951 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001952
Benny Prijono8f310522006-10-20 11:08:49 +00001953 /* Don't support encoding type at present */
1954 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001955
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001956 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
1957 return PJ_ETOOMANY;
1958
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001959 /* Determine the file format */
1960 ext.ptr = filename->ptr + filename->slen - 4;
1961 ext.slen = 4;
1962
1963 if (pj_stricmp2(&ext, ".wav") == 0)
1964 file_format = FMT_WAV;
1965 else if (pj_stricmp2(&ext, ".mp3") == 0)
1966 file_format = FMT_MP3;
1967 else {
1968 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
1969 "determine file format for %.*s",
1970 (int)filename->slen, filename->ptr));
1971 return PJ_ENOTSUP;
1972 }
1973
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001974 PJSUA_LOCK();
1975
1976 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
1977 if (pjsua_var.recorder[file_id].port == NULL)
1978 break;
1979 }
1980
1981 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
1982 /* This is unexpected */
1983 PJSUA_UNLOCK();
1984 pj_assert(0);
1985 return PJ_EBUG;
1986 }
1987
1988 pj_memcpy(path, filename->ptr, filename->slen);
1989 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001990
Benny Prijonod5696da2007-07-17 16:25:45 +00001991 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1992 if (!pool) {
1993 PJSUA_UNLOCK();
1994 return PJ_ENOMEM;
1995 }
1996
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001997 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00001998 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001999 pjsua_var.media_cfg.clock_rate,
2000 pjsua_var.mconf_cfg.channel_count,
2001 pjsua_var.mconf_cfg.samples_per_frame,
2002 pjsua_var.mconf_cfg.bits_per_sample,
2003 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002004 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00002005 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002006 port = NULL;
2007 status = PJ_ENOTSUP;
2008 }
2009
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002010 if (status != PJ_SUCCESS) {
2011 PJSUA_UNLOCK();
2012 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002013 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002014 return status;
2015 }
2016
Benny Prijonod5696da2007-07-17 16:25:45 +00002017 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002018 port, filename, &slot);
2019 if (status != PJ_SUCCESS) {
2020 pjmedia_port_destroy(port);
2021 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00002022 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002023 return status;
2024 }
2025
2026 pjsua_var.recorder[file_id].port = port;
2027 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00002028 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002029
2030 if (p_id) *p_id = file_id;
2031
2032 ++pjsua_var.rec_cnt;
2033
2034 PJSUA_UNLOCK();
2035 return PJ_SUCCESS;
2036}
2037
2038
2039/*
2040 * Get conference port associated with recorder.
2041 */
2042PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
2043{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002044 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2045 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002046 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2047
2048 return pjsua_var.recorder[id].slot;
2049}
2050
Benny Prijono469b1522006-12-26 03:05:17 +00002051/*
2052 * Get the media port for the recorder.
2053 */
2054PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
2055 pjmedia_port **p_port)
2056{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002057 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2058 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002059 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2060 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2061
2062 *p_port = pjsua_var.recorder[id].port;
2063 return PJ_SUCCESS;
2064}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002065
2066/*
2067 * Destroy recorder (this will complete recording).
2068 */
2069PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
2070{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002071 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2072 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002073 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2074
2075 PJSUA_LOCK();
2076
2077 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002078 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002079 pjmedia_port_destroy(pjsua_var.recorder[id].port);
2080 pjsua_var.recorder[id].port = NULL;
2081 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002082 pj_pool_release(pjsua_var.recorder[id].pool);
2083 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002084 pjsua_var.rec_cnt--;
2085 }
2086
2087 PJSUA_UNLOCK();
2088
2089 return PJ_SUCCESS;
2090}
2091
2092
2093/*****************************************************************************
2094 * Sound devices.
2095 */
2096
2097/*
2098 * Enum sound devices.
2099 */
2100PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
2101 unsigned *count)
2102{
2103 unsigned i, dev_count;
2104
2105 dev_count = pjmedia_snd_get_dev_count();
2106
2107 if (dev_count > *count) dev_count = *count;
2108
2109 for (i=0; i<dev_count; ++i) {
2110 const pjmedia_snd_dev_info *ci;
2111
2112 ci = pjmedia_snd_get_dev_info(i);
2113 pj_memcpy(&info[i], ci, sizeof(*ci));
2114 }
2115
2116 *count = dev_count;
2117
2118 return PJ_SUCCESS;
2119}
2120
2121
2122/* Close existing sound device */
2123static void close_snd_dev(void)
2124{
2125 /* Close sound device */
2126 if (pjsua_var.snd_port) {
2127 const pjmedia_snd_dev_info *cap_info, *play_info;
2128
2129 cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
2130 play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
2131
2132 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
2133 "%s sound capture device",
2134 play_info->name, cap_info->name));
2135
2136 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
2137 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2138 pjsua_var.snd_port = NULL;
2139 }
2140
2141 /* Close null sound device */
2142 if (pjsua_var.null_snd) {
2143 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
2144 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
2145 pjsua_var.null_snd = NULL;
2146 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002147
2148 if (pjsua_var.snd_pool)
2149 pj_pool_release(pjsua_var.snd_pool);
2150 pjsua_var.snd_pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002151}
2152
2153/*
2154 * Select or change sound device. Application may call this function at
2155 * any time to replace current sound device.
2156 */
2157PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
2158 int playback_dev)
2159{
2160 pjmedia_port *conf_port;
Benny Prijono6dd967c2006-12-26 02:27:14 +00002161 const pjmedia_snd_dev_info *play_info;
Nanang Izzuddin6aa44952008-10-07 12:42:24 +00002162 unsigned clock_rates[] = {0, 44100, 48000, 32000, 16000, 8000};
Benny Prijono658a1c52006-10-11 21:56:16 +00002163 unsigned selected_clock_rate = 0;
Benny Prijono26056d82006-10-11 16:03:41 +00002164 unsigned i;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002165 pjmedia_snd_stream *strm;
2166 pjmedia_snd_stream_info si;
2167 pj_str_t tmp;
Benny Prijono26056d82006-10-11 16:03:41 +00002168 pj_status_t status = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002169
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002170 /* Check if NULL sound device is used */
2171 if (NULL_SND_DEV_ID == capture_dev || NULL_SND_DEV_ID == playback_dev) {
2172 return pjsua_set_null_snd_dev();
2173 }
2174
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002175 /* Close existing sound port */
2176 close_snd_dev();
2177
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002178 /* Create memory pool for sound device. */
2179 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2180 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002181
Benny Prijono26056d82006-10-11 16:03:41 +00002182 /* Set default clock rate */
Benny Prijonof3758ee2008-02-26 15:32:16 +00002183 clock_rates[0] = pjsua_var.media_cfg.snd_clock_rate;
2184 if (clock_rates[0] == 0)
2185 clock_rates[0] = pjsua_var.media_cfg.clock_rate;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002186
Benny Prijono50f19b32008-03-11 13:15:43 +00002187 /* Get the port0 of the conference bridge. */
2188 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2189 pj_assert(conf_port != NULL);
2190
Benny Prijono26056d82006-10-11 16:03:41 +00002191 /* Attempts to open the sound device with different clock rates */
2192 for (i=0; i<PJ_ARRAY_SIZE(clock_rates); ++i) {
2193 char errmsg[PJ_ERR_MSG_SIZE];
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002194 unsigned samples_per_frame;
Benny Prijono26056d82006-10-11 16:03:41 +00002195
2196 PJ_LOG(4,(THIS_FILE,
2197 "pjsua_set_snd_dev(): attempting to open devices "
2198 "@%d Hz", clock_rates[i]));
2199
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002200 samples_per_frame = clock_rates[i] *
2201 pjsua_var.media_cfg.audio_frame_ptime *
2202 pjsua_var.media_cfg.channel_count / 1000;
2203
Benny Prijono26056d82006-10-11 16:03:41 +00002204 /* Create the sound device. Sound port will start immediately. */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002205 status = pjmedia_snd_port_create(pjsua_var.snd_pool, capture_dev,
Benny Prijono26056d82006-10-11 16:03:41 +00002206 playback_dev,
Benny Prijono7d60d052008-03-29 12:24:20 +00002207 clock_rates[i],
2208 pjsua_var.media_cfg.channel_count,
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002209 samples_per_frame,
Benny Prijono26056d82006-10-11 16:03:41 +00002210 16, 0, &pjsua_var.snd_port);
2211
Benny Prijono658a1c52006-10-11 21:56:16 +00002212 if (status == PJ_SUCCESS) {
2213 selected_clock_rate = clock_rates[i];
Benny Prijono50f19b32008-03-11 13:15:43 +00002214
2215 /* If there's mismatch between sound port and conference's port,
2216 * create a resample port to bridge them.
2217 */
2218 if (selected_clock_rate != pjsua_var.media_cfg.clock_rate) {
2219 pjmedia_port *resample_port;
2220 unsigned resample_opt = 0;
2221
2222 if (pjsua_var.media_cfg.quality >= 3 &&
2223 pjsua_var.media_cfg.quality <= 4)
2224 {
Nanang Izzuddine85a1832008-05-29 11:03:23 +00002225 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
Benny Prijono50f19b32008-03-11 13:15:43 +00002226 }
2227 else if (pjsua_var.media_cfg.quality < 3) {
Nanang Izzuddine85a1832008-05-29 11:03:23 +00002228 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
Benny Prijono50f19b32008-03-11 13:15:43 +00002229 }
2230
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002231 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
Benny Prijono50f19b32008-03-11 13:15:43 +00002232 conf_port,
2233 selected_clock_rate,
2234 resample_opt,
2235 &resample_port);
2236 if (status != PJ_SUCCESS) {
2237 pj_strerror(status, errmsg, sizeof(errmsg));
2238 PJ_LOG(4, (THIS_FILE,
2239 "Error creating resample port, trying next "
2240 "clock rate",
2241 errmsg));
2242 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2243 pjsua_var.snd_port = NULL;
2244 continue;
2245 } else {
2246 conf_port = resample_port;
2247 break;
2248 }
2249
2250 } else {
2251 break;
2252 }
Benny Prijono658a1c52006-10-11 21:56:16 +00002253 }
Benny Prijono26056d82006-10-11 16:03:41 +00002254
2255 pj_strerror(status, errmsg, sizeof(errmsg));
Benny Prijono658a1c52006-10-11 21:56:16 +00002256 PJ_LOG(4, (THIS_FILE, "..failed: %s", errmsg));
Benny Prijono26056d82006-10-11 16:03:41 +00002257 }
2258
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002259 if (status != PJ_SUCCESS) {
2260 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
2261 return status;
2262 }
2263
Benny Prijonof20687a2006-08-04 18:27:19 +00002264 /* Set AEC */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002265 pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.snd_pool,
Benny Prijono5da50432006-08-07 10:24:52 +00002266 pjsua_var.media_cfg.ec_tail_len,
2267 pjsua_var.media_cfg.ec_options);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002268
Benny Prijono52a93912006-08-04 20:54:37 +00002269 /* Connect sound port to the bridge */
2270 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
2271 conf_port );
2272 if (status != PJ_SUCCESS) {
2273 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
2274 "sound device", status);
2275 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2276 pjsua_var.snd_port = NULL;
2277 return status;
2278 }
2279
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002280 /* Save the device IDs */
2281 pjsua_var.cap_dev = capture_dev;
2282 pjsua_var.play_dev = playback_dev;
2283
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002284 /* Update sound device name. */
2285 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2286 pjmedia_snd_stream_get_info(strm, &si);
2287 play_info = pjmedia_snd_get_dev_info(si.rec_id);
2288
Benny Prijonof3758ee2008-02-26 15:32:16 +00002289 if (si.clock_rate != pjsua_var.media_cfg.clock_rate) {
2290 char tmp_buf[128];
2291 int tmp_buf_len = sizeof(tmp_buf);
2292
2293 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1, "%s (%dKHz)",
2294 play_info->name, si.clock_rate/1000);
2295 pj_strset(&tmp, tmp_buf, tmp_buf_len);
2296 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
2297 } else {
2298 pjmedia_conf_set_port0_name(pjsua_var.mconf,
2299 pj_cstr(&tmp, play_info->name));
2300 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002301
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002302 return PJ_SUCCESS;
2303}
2304
2305
2306/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00002307 * Get currently active sound devices. If sound devices has not been created
2308 * (for example when pjsua_start() is not called), it is possible that
2309 * the function returns PJ_SUCCESS with -1 as device IDs.
2310 */
2311PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
2312 int *playback_dev)
2313{
2314 if (capture_dev) {
2315 *capture_dev = pjsua_var.cap_dev;
2316 }
2317 if (playback_dev) {
2318 *playback_dev = pjsua_var.play_dev;
2319 }
2320
2321 return PJ_SUCCESS;
2322}
2323
2324
2325/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002326 * Use null sound device.
2327 */
2328PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
2329{
2330 pjmedia_port *conf_port;
2331 pj_status_t status;
2332
2333 /* Close existing sound device */
2334 close_snd_dev();
2335
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002336 /* Create memory pool for sound device. */
2337 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2338 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2339
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002340 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
2341
2342 /* Get the port0 of the conference bridge. */
2343 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2344 pj_assert(conf_port != NULL);
2345
2346 /* Create master port, connecting port0 of the conference bridge to
2347 * a null port.
2348 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002349 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002350 conf_port, 0, &pjsua_var.null_snd);
2351 if (status != PJ_SUCCESS) {
2352 pjsua_perror(THIS_FILE, "Unable to create null sound device",
2353 status);
2354 return status;
2355 }
2356
2357 /* Start the master port */
2358 status = pjmedia_master_port_start(pjsua_var.null_snd);
2359 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2360
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002361 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2362 pjsua_var.play_dev = NULL_SND_DEV_ID;
2363
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002364 return PJ_SUCCESS;
2365}
2366
2367
Benny Prijonoe909eac2006-07-27 22:04:56 +00002368
2369/*
2370 * Use no device!
2371 */
2372PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2373{
2374 /* Close existing sound device */
2375 close_snd_dev();
2376
2377 pjsua_var.no_snd = PJ_TRUE;
2378 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2379}
2380
2381
Benny Prijonof20687a2006-08-04 18:27:19 +00002382/*
2383 * Configure the AEC settings of the sound port.
2384 */
Benny Prijono5da50432006-08-07 10:24:52 +00002385PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00002386{
2387 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2388
2389 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00002390 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
2391 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00002392
2393 return PJ_SUCCESS;
2394}
2395
2396
2397/*
2398 * Get current AEC tail length.
2399 */
Benny Prijono22dfe592006-08-06 12:07:13 +00002400PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00002401{
2402 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2403 return PJ_SUCCESS;
2404}
2405
Benny Prijonoe909eac2006-07-27 22:04:56 +00002406
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002407/*****************************************************************************
2408 * Codecs.
2409 */
2410
2411/*
2412 * Enum all supported codecs in the system.
2413 */
2414PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
2415 unsigned *p_count )
2416{
2417 pjmedia_codec_mgr *codec_mgr;
2418 pjmedia_codec_info info[32];
2419 unsigned i, count, prio[32];
2420 pj_status_t status;
2421
2422 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2423 count = PJ_ARRAY_SIZE(info);
2424 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
2425 if (status != PJ_SUCCESS) {
2426 *p_count = 0;
2427 return status;
2428 }
2429
2430 if (count > *p_count) count = *p_count;
2431
2432 for (i=0; i<count; ++i) {
2433 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
2434 id[i].codec_id = pj_str(id[i].buf_);
2435 id[i].priority = (pj_uint8_t) prio[i];
2436 }
2437
2438 *p_count = count;
2439
2440 return PJ_SUCCESS;
2441}
2442
2443
2444/*
2445 * Change codec priority.
2446 */
2447PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
2448 pj_uint8_t priority )
2449{
Benny Prijono88accae2008-06-26 15:48:14 +00002450 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002451 pjmedia_codec_mgr *codec_mgr;
2452
2453 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2454
Benny Prijono88accae2008-06-26 15:48:14 +00002455 if (codec_id->slen==1 && *codec_id->ptr=='*')
2456 codec_id = &all;
2457
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002458 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
2459 priority);
2460}
2461
2462
2463/*
2464 * Get codec parameters.
2465 */
2466PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
2467 pjmedia_codec_param *param )
2468{
Benny Prijono88accae2008-06-26 15:48:14 +00002469 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002470 const pjmedia_codec_info *info;
2471 pjmedia_codec_mgr *codec_mgr;
2472 unsigned count = 1;
2473 pj_status_t status;
2474
2475 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2476
Benny Prijono88accae2008-06-26 15:48:14 +00002477 if (codec_id->slen==1 && *codec_id->ptr=='*')
2478 codec_id = &all;
2479
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002480 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
2481 &count, &info, NULL);
2482 if (status != PJ_SUCCESS)
2483 return status;
2484
2485 if (count != 1)
2486 return PJ_ENOTFOUND;
2487
2488 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
2489 return status;
2490}
2491
2492
2493/*
2494 * Set codec parameters.
2495 */
2496PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
2497 const pjmedia_codec_param *param)
2498{
Benny Prijono00cae612006-07-31 15:19:36 +00002499 PJ_UNUSED_ARG(id);
2500 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002501 PJ_TODO(set_codec_param);
2502 return PJ_SUCCESS;
2503}