blob: 053603050bff5f8b272dfec175c2042318e87bf6 [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
Benny Prijono32177c02008-06-20 22:44:47 +00003 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjsua-lib/pjsua.h>
20#include <pjsua-lib/pjsua_internal.h>
21
22
23#define THIS_FILE "pjsua_media.c"
24
Nanang Izzuddin68559c32008-06-13 17:01:46 +000025#define DEFAULT_RTP_PORT 4000
26
27#define NULL_SND_DEV_ID -99
Benny Prijono80eee892007-11-03 22:43:23 +000028
29#ifndef PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
30# define PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT 0
31#endif
32
Benny Prijonoeebe9af2006-06-13 22:57:13 +000033
Benny Prijonode479562007-03-15 10:23:55 +000034/* Next RTP port to be used */
35static pj_uint16_t next_rtp_port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000036
37/* Close existing sound device */
38static void close_snd_dev(void);
39
40
Benny Prijonof76e1392008-06-06 14:51:48 +000041static void pjsua_media_config_dup(pj_pool_t *pool,
42 pjsua_media_config *dst,
43 const pjsua_media_config *src)
44{
45 pj_memcpy(dst, src, sizeof(*src));
46 pj_strdup(pool, &dst->turn_server, &src->turn_server);
47 pj_stun_auth_cred_dup(pool, &dst->turn_auth_cred, &src->turn_auth_cred);
48}
49
Benny Prijonoeebe9af2006-06-13 22:57:13 +000050/**
51 * Init media subsystems.
52 */
53pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
54{
Benny Prijonoba5926a2007-05-02 11:29:37 +000055 pj_str_t codec_id = {NULL, 0};
Benny Prijono0498d902006-06-19 14:49:14 +000056 unsigned opt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000057 pj_status_t status;
58
Benny Prijonofc24e692007-01-27 18:31:51 +000059 /* To suppress warning about unused var when all codecs are disabled */
60 PJ_UNUSED_ARG(codec_id);
61
Benny Prijonoeebe9af2006-06-13 22:57:13 +000062 /* Copy configuration */
Benny Prijonof76e1392008-06-06 14:51:48 +000063 pjsua_media_config_dup(pjsua_var.pool, &pjsua_var.media_cfg, cfg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000064
65 /* Normalize configuration */
Benny Prijono50f19b32008-03-11 13:15:43 +000066 if (pjsua_var.media_cfg.snd_clock_rate == 0) {
67 pjsua_var.media_cfg.snd_clock_rate = pjsua_var.media_cfg.clock_rate;
68 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +000069
70 if (pjsua_var.media_cfg.has_ioqueue &&
71 pjsua_var.media_cfg.thread_cnt == 0)
72 {
73 pjsua_var.media_cfg.thread_cnt = 1;
74 }
75
76 if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
77 pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
78 }
79
80 /* Create media endpoint. */
81 status = pjmedia_endpt_create(&pjsua_var.cp.factory,
82 pjsua_var.media_cfg.has_ioqueue? NULL :
83 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
84 pjsua_var.media_cfg.thread_cnt,
85 &pjsua_var.med_endpt);
86 if (status != PJ_SUCCESS) {
87 pjsua_perror(THIS_FILE,
88 "Media stack initialization has returned error",
89 status);
90 return status;
91 }
92
93 /* Register all codecs */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +000094
Benny Prijonoeebe9af2006-06-13 22:57:13 +000095#if PJMEDIA_HAS_SPEEX_CODEC
96 /* Register speex. */
Nanang Izzuddin9dbad152008-06-10 18:56:10 +000097 status = pjmedia_codec_speex_init(pjsua_var.med_endpt,
98 0,
99 pjsua_var.media_cfg.quality,
100 -1);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000101 if (status != PJ_SUCCESS) {
102 pjsua_perror(THIS_FILE, "Error initializing Speex codec",
103 status);
104 return status;
105 }
Benny Prijono7ca96da2006-08-07 12:11:40 +0000106
107 /* Set speex/16000 to higher priority*/
108 codec_id = pj_str("speex/16000");
109 pjmedia_codec_mgr_set_codec_priority(
110 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
111 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
112
113 /* Set speex/8000 to next higher priority*/
114 codec_id = pj_str("speex/8000");
115 pjmedia_codec_mgr_set_codec_priority(
116 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
117 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
118
119
120
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000121#endif /* PJMEDIA_HAS_SPEEX_CODEC */
122
Benny Prijono00cae612006-07-31 15:19:36 +0000123#if PJMEDIA_HAS_ILBC_CODEC
124 /* Register iLBC. */
125 status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt,
126 pjsua_var.media_cfg.ilbc_mode);
127 if (status != PJ_SUCCESS) {
128 pjsua_perror(THIS_FILE, "Error initializing iLBC codec",
129 status);
130 return status;
131 }
132#endif /* PJMEDIA_HAS_ILBC_CODEC */
133
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000134#if PJMEDIA_HAS_GSM_CODEC
135 /* Register GSM */
136 status = pjmedia_codec_gsm_init(pjsua_var.med_endpt);
137 if (status != PJ_SUCCESS) {
138 pjsua_perror(THIS_FILE, "Error initializing GSM codec",
139 status);
140 return status;
141 }
142#endif /* PJMEDIA_HAS_GSM_CODEC */
143
144#if PJMEDIA_HAS_G711_CODEC
145 /* Register PCMA and PCMU */
146 status = pjmedia_codec_g711_init(pjsua_var.med_endpt);
147 if (status != PJ_SUCCESS) {
148 pjsua_perror(THIS_FILE, "Error initializing G711 codec",
149 status);
150 return status;
151 }
152#endif /* PJMEDIA_HAS_G711_CODEC */
153
Benny Prijono7ffd7752008-03-17 14:07:53 +0000154#if PJMEDIA_HAS_G722_CODEC
155 status = pjmedia_codec_g722_init( pjsua_var.med_endpt );
156 if (status != PJ_SUCCESS) {
157 pjsua_perror(THIS_FILE, "Error initializing G722 codec",
158 status);
159 return status;
160 }
161#endif /* PJMEDIA_HAS_G722_CODEC */
162
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000163#if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000164 /* Register IPP codecs */
165 status = pjmedia_codec_ipp_init(pjsua_var.med_endpt);
166 if (status != PJ_SUCCESS) {
167 pjsua_perror(THIS_FILE, "Error initializing IPP codecs",
168 status);
169 return status;
170 }
171
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000172#endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000173
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000174#if PJMEDIA_HAS_L16_CODEC
175 /* Register L16 family codecs, but disable all */
176 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
177 if (status != PJ_SUCCESS) {
178 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
179 status);
180 return status;
181 }
182
183 /* Disable ALL L16 codecs */
184 codec_id = pj_str("L16");
185 pjmedia_codec_mgr_set_codec_priority(
186 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
187 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
188
189#endif /* PJMEDIA_HAS_L16_CODEC */
190
191
192 /* Save additional conference bridge parameters for future
193 * reference.
194 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000195 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000196 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000197 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
198 pjsua_var.mconf_cfg.channel_count *
199 pjsua_var.media_cfg.audio_frame_ptime /
200 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000201
Benny Prijono0498d902006-06-19 14:49:14 +0000202 /* Init options for conference bridge. */
203 opt = PJMEDIA_CONF_NO_DEVICE;
204 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000205 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000206 {
207 opt |= PJMEDIA_CONF_SMALL_FILTER;
208 }
209 else if (pjsua_var.media_cfg.quality < 3) {
210 opt |= PJMEDIA_CONF_USE_LINEAR;
211 }
212
213
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000214 /* Init conference bridge. */
215 status = pjmedia_conf_create(pjsua_var.pool,
216 pjsua_var.media_cfg.max_media_ports,
217 pjsua_var.media_cfg.clock_rate,
218 pjsua_var.mconf_cfg.channel_count,
219 pjsua_var.mconf_cfg.samples_per_frame,
220 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000221 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000222 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000223 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000224 status);
225 return status;
226 }
227
228 /* Create null port just in case user wants to use null sound. */
229 status = pjmedia_null_port_create(pjsua_var.pool,
230 pjsua_var.media_cfg.clock_rate,
231 pjsua_var.mconf_cfg.channel_count,
232 pjsua_var.mconf_cfg.samples_per_frame,
233 pjsua_var.mconf_cfg.bits_per_sample,
234 &pjsua_var.null_port);
235 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
236
Benny Prijono6ba8c542007-10-16 01:34:14 +0000237 /* Perform NAT detection */
238 pjsua_detect_nat_type();
239
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000240 return PJ_SUCCESS;
241}
242
243
244/*
245 * Create RTP and RTCP socket pair, and possibly resolve their public
246 * address via STUN.
247 */
248static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
249 pjmedia_sock_info *skinfo)
250{
251 enum {
252 RTP_RETRY = 100
253 };
254 int i;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000255 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000256 pj_sockaddr_in mapped_addr[2];
257 pj_status_t status = PJ_SUCCESS;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000258 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000259 pj_sock_t sock[2];
260
Benny Prijonoc97608e2007-03-23 16:34:20 +0000261 /* Make sure STUN server resolution has completed */
262 status = pjsua_resolve_stun_server(PJ_TRUE);
263 if (status != PJ_SUCCESS) {
264 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
265 return status;
266 }
267
Benny Prijonode479562007-03-15 10:23:55 +0000268 if (next_rtp_port == 0)
269 next_rtp_port = (pj_uint16_t)cfg->port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000270
271 for (i=0; i<2; ++i)
272 sock[i] = PJ_INVALID_SOCKET;
273
Benny Prijono0a5cad82006-09-26 13:21:02 +0000274 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
275 if (cfg->bound_addr.slen) {
276 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
277 if (status != PJ_SUCCESS) {
278 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
279 status);
280 return status;
281 }
282 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000283
284 /* Loop retry to bind RTP and RTCP sockets. */
Benny Prijonode479562007-03-15 10:23:55 +0000285 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000286
287 /* Create and bind RTP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000288 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000289 if (status != PJ_SUCCESS) {
290 pjsua_perror(THIS_FILE, "socket() error", status);
291 return status;
292 }
293
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000294 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
295 next_rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000296 if (status != PJ_SUCCESS) {
297 pj_sock_close(sock[0]);
298 sock[0] = PJ_INVALID_SOCKET;
299 continue;
300 }
301
302 /* Create and bind RTCP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000303 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000304 if (status != PJ_SUCCESS) {
305 pjsua_perror(THIS_FILE, "socket() error", status);
306 pj_sock_close(sock[0]);
307 return status;
308 }
309
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000310 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
311 (pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000312 if (status != PJ_SUCCESS) {
313 pj_sock_close(sock[0]);
314 sock[0] = PJ_INVALID_SOCKET;
315
316 pj_sock_close(sock[1]);
317 sock[1] = PJ_INVALID_SOCKET;
318 continue;
319 }
320
321 /*
322 * If we're configured to use STUN, then find out the mapped address,
323 * and make sure that the mapped RTCP port is adjacent with the RTP.
324 */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000325 if (pjsua_var.stun_srv.addr.sa_family != 0) {
326 char ip_addr[32];
327 pj_str_t stun_srv;
328
329 pj_ansi_strcpy(ip_addr,
330 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
331 stun_srv = pj_str(ip_addr);
332
Benny Prijono14c2b862007-02-21 00:40:05 +0000333 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000334 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
335 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000336 mapped_addr);
337 if (status != PJ_SUCCESS) {
338 pjsua_perror(THIS_FILE, "STUN resolve error", status);
339 goto on_error;
340 }
341
Benny Prijono80eee892007-11-03 22:43:23 +0000342#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000343 if (pj_ntohs(mapped_addr[1].sin_port) ==
344 pj_ntohs(mapped_addr[0].sin_port)+1)
345 {
346 /* Success! */
347 break;
348 }
349
350 pj_sock_close(sock[0]);
351 sock[0] = PJ_INVALID_SOCKET;
352
353 pj_sock_close(sock[1]);
354 sock[1] = PJ_INVALID_SOCKET;
Benny Prijono80eee892007-11-03 22:43:23 +0000355#else
356 if (pj_ntohs(mapped_addr[1].sin_port) !=
357 pj_ntohs(mapped_addr[0].sin_port)+1)
358 {
359 PJ_LOG(4,(THIS_FILE,
360 "Note: STUN mapped RTCP port %d is not adjacent"
361 " to RTP port %d",
362 pj_ntohs(mapped_addr[1].sin_port),
363 pj_ntohs(mapped_addr[0].sin_port)));
364 }
365 /* Success! */
366 break;
367#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000368
Benny Prijono0a5cad82006-09-26 13:21:02 +0000369 } else if (cfg->public_addr.slen) {
370
371 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000372 (pj_uint16_t)next_rtp_port);
Benny Prijono0a5cad82006-09-26 13:21:02 +0000373 if (status != PJ_SUCCESS)
374 goto on_error;
375
376 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000377 (pj_uint16_t)(next_rtp_port+1));
Benny Prijono0a5cad82006-09-26 13:21:02 +0000378 if (status != PJ_SUCCESS)
379 goto on_error;
380
381 break;
382
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000383 } else {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000384
Benny Prijono42d08d22007-12-20 11:23:07 +0000385 if (bound_addr.sin_addr.s_addr == 0) {
386 pj_sockaddr addr;
387
388 /* Get local IP address. */
389 status = pj_gethostip(pj_AF_INET(), &addr);
390 if (status != PJ_SUCCESS)
391 goto on_error;
392
393 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
394 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000395
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000396 for (i=0; i<2; ++i) {
397 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
Benny Prijono42d08d22007-12-20 11:23:07 +0000398 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000399 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000400
Benny Prijonode479562007-03-15 10:23:55 +0000401 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
402 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000403 break;
404 }
405 }
406
407 if (sock[0] == PJ_INVALID_SOCKET) {
408 PJ_LOG(1,(THIS_FILE,
409 "Unable to find appropriate RTP/RTCP ports combination"));
410 goto on_error;
411 }
412
413
414 skinfo->rtp_sock = sock[0];
415 pj_memcpy(&skinfo->rtp_addr_name,
416 &mapped_addr[0], sizeof(pj_sockaddr_in));
417
418 skinfo->rtcp_sock = sock[1];
419 pj_memcpy(&skinfo->rtcp_addr_name,
420 &mapped_addr[1], sizeof(pj_sockaddr_in));
421
Benny Prijono8b22ce12008-02-08 12:57:55 +0000422 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000423 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
424 sizeof(addr_buf), 3)));
Benny Prijono8b22ce12008-02-08 12:57:55 +0000425 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000426 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
427 sizeof(addr_buf), 3)));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000428
Benny Prijonode479562007-03-15 10:23:55 +0000429 next_rtp_port += 2;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000430 return PJ_SUCCESS;
431
432on_error:
433 for (i=0; i<2; ++i) {
434 if (sock[i] != PJ_INVALID_SOCKET)
435 pj_sock_close(sock[i]);
436 }
437 return status;
438}
439
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000440/* Check if sound device is idle. */
441static void check_snd_dev_idle()
442{
443
444 /* Activate sound device auto-close timer if sound device is idle.
445 * It is idle when there is no port connection in the bridge.
446 */
447 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) &&
448 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
449 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
450 pjsua_var.media_cfg.snd_auto_close_time >= 0)
451 {
452 pj_time_val delay;
453
454 delay.msec = 0;
455 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
456
457 pjsua_var.snd_idle_timer.id = PJ_TRUE;
458 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
459 &delay);
460 }
461}
462
463
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000464/* Timer callback to close sound device */
465static void close_snd_timer_cb( pj_timer_heap_t *th,
466 pj_timer_entry *entry)
467{
468 PJ_UNUSED_ARG(th);
469
470 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
471 pjsua_var.media_cfg.snd_auto_close_time));
472
473 entry->id = PJ_FALSE;
474
475 close_snd_dev();
476}
477
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000478
479/*
480 * Start pjsua media subsystem.
481 */
482pj_status_t pjsua_media_subsys_start(void)
483{
484 pj_status_t status;
485
486 /* Create media for calls, if none is specified */
487 if (pjsua_var.calls[0].med_tp == NULL) {
488 pjsua_transport_config transport_cfg;
489
490 /* Create default transport config */
491 pjsua_transport_config_default(&transport_cfg);
492 transport_cfg.port = DEFAULT_RTP_PORT;
493
494 status = pjsua_media_transports_create(&transport_cfg);
495 if (status != PJ_SUCCESS)
496 return status;
497 }
498
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000499 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
500 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000501
502 return PJ_SUCCESS;
503}
504
505
506/*
507 * Destroy pjsua media subsystem.
508 */
509pj_status_t pjsua_media_subsys_destroy(void)
510{
511 unsigned i;
512
513 close_snd_dev();
514
515 if (pjsua_var.mconf) {
516 pjmedia_conf_destroy(pjsua_var.mconf);
517 pjsua_var.mconf = NULL;
518 }
519
520 if (pjsua_var.null_port) {
521 pjmedia_port_destroy(pjsua_var.null_port);
522 pjsua_var.null_port = NULL;
523 }
524
525 /* Destroy file players */
526 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
527 if (pjsua_var.player[i].port) {
528 pjmedia_port_destroy(pjsua_var.player[i].port);
529 pjsua_var.player[i].port = NULL;
530 }
531 }
532
533 /* Destroy file recorders */
534 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
535 if (pjsua_var.recorder[i].port) {
536 pjmedia_port_destroy(pjsua_var.recorder[i].port);
537 pjsua_var.recorder[i].port = NULL;
538 }
539 }
540
541 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000542 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono311b63f2008-07-14 11:31:40 +0000543 if (pjsua_var.calls[i].med_tp_st != PJSUA_MED_TP_IDLE) {
544 pjsua_media_channel_deinit(i);
545 }
Benny Prijono40860c32008-09-04 13:55:33 +0000546 if (pjsua_var.calls[i].med_tp && pjsua_var.calls[i].med_tp_auto_del) {
547 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000548 }
Benny Prijono40860c32008-09-04 13:55:33 +0000549 pjsua_var.calls[i].med_tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000550 }
551
552 /* Destroy media endpoint. */
553 if (pjsua_var.med_endpt) {
554
555 /* Shutdown all codecs: */
556# if PJMEDIA_HAS_SPEEX_CODEC
557 pjmedia_codec_speex_deinit();
558# endif /* PJMEDIA_HAS_SPEEX_CODEC */
559
560# if PJMEDIA_HAS_GSM_CODEC
561 pjmedia_codec_gsm_deinit();
562# endif /* PJMEDIA_HAS_GSM_CODEC */
563
564# if PJMEDIA_HAS_G711_CODEC
565 pjmedia_codec_g711_deinit();
566# endif /* PJMEDIA_HAS_G711_CODEC */
567
Benny Prijono7ffd7752008-03-17 14:07:53 +0000568# if PJMEDIA_HAS_G722_CODEC
569 pjmedia_codec_g722_deinit();
570# endif /* PJMEDIA_HAS_G722_CODEC */
571
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000572# if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000573 pjmedia_codec_ipp_deinit();
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000574# endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000575
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000576# if PJMEDIA_HAS_L16_CODEC
577 pjmedia_codec_l16_deinit();
578# endif /* PJMEDIA_HAS_L16_CODEC */
579
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000580 pjmedia_endpt_destroy(pjsua_var.med_endpt);
581 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000582
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000583 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000584 // Not necessary, as pjmedia_snd_deinit() should have been called
585 // in pjmedia_endpt_destroy().
586 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000587 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000588
Benny Prijonode479562007-03-15 10:23:55 +0000589 /* Reset RTP port */
590 next_rtp_port = 0;
591
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000592 return PJ_SUCCESS;
593}
594
595
Benny Prijonoc97608e2007-03-23 16:34:20 +0000596/* Create normal UDP media transports */
597static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000598{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000599 unsigned i;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000600 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000601 pj_status_t status;
602
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000603 /* Create each media transport */
604 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
605
Benny Prijono617c5bc2007-04-02 19:51:21 +0000606 status = create_rtp_rtcp_sock(cfg, &skinfo);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000607 if (status != PJ_SUCCESS) {
608 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
609 status);
610 goto on_error;
611 }
Benny Prijonod8179652008-01-23 20:39:07 +0000612
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000613 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000614 &skinfo, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000615 &pjsua_var.calls[i].med_tp);
616 if (status != PJ_SUCCESS) {
617 pjsua_perror(THIS_FILE, "Unable to create media transport",
618 status);
619 goto on_error;
620 }
Benny Prijono00cae612006-07-31 15:19:36 +0000621
Benny Prijonod8179652008-01-23 20:39:07 +0000622 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
623 PJMEDIA_DIR_ENCODING,
624 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000625
Benny Prijonod8179652008-01-23 20:39:07 +0000626 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
627 PJMEDIA_DIR_DECODING,
628 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000629
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000630 }
631
Benny Prijonoc97608e2007-03-23 16:34:20 +0000632 return PJ_SUCCESS;
633
634on_error:
635 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
636 if (pjsua_var.calls[i].med_tp != NULL) {
637 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
638 pjsua_var.calls[i].med_tp = NULL;
639 }
640 }
641
642 return status;
643}
644
645
Benny Prijono096c56c2007-09-15 08:30:16 +0000646/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000647static void on_ice_complete(pjmedia_transport *tp,
648 pj_ice_strans_op op,
649 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000650{
Benny Prijonof76e1392008-06-06 14:51:48 +0000651 unsigned id;
Benny Prijono096c56c2007-09-15 08:30:16 +0000652 pj_bool_t found = PJ_FALSE;
653
Benny Prijono096c56c2007-09-15 08:30:16 +0000654 /* Find call which has this media transport */
655
656 PJSUA_LOCK();
657
Benny Prijonof76e1392008-06-06 14:51:48 +0000658 for (id=0; id<pjsua_var.ua_cfg.max_calls; ++id) {
659 if (pjsua_var.calls[id].med_tp == tp ||
660 pjsua_var.calls[id].med_orig == tp)
661 {
662 found = PJ_TRUE;
663 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000664 }
665 }
666
667 PJSUA_UNLOCK();
668
Benny Prijonof76e1392008-06-06 14:51:48 +0000669 if (!found)
670 return;
671
672 switch (op) {
673 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono224b4e22008-06-19 14:10:28 +0000674 pjsua_var.calls[id].med_tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000675 break;
676 case PJ_ICE_STRANS_OP_NEGOTIATION:
677 if (result != PJ_SUCCESS) {
678 pjsua_var.calls[id].media_st = PJSUA_CALL_MEDIA_ERROR;
679 pjsua_var.calls[id].media_dir = PJMEDIA_DIR_NONE;
680
681 if (pjsua_var.ua_cfg.cb.on_call_media_state) {
682 pjsua_var.ua_cfg.cb.on_call_media_state(id);
683 }
684 }
685 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000686 }
687}
688
689
Benny Prijonof76e1392008-06-06 14:51:48 +0000690/* Parse "HOST:PORT" format */
691static pj_status_t parse_host_port(const pj_str_t *host_port,
692 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000693{
Benny Prijonof76e1392008-06-06 14:51:48 +0000694 pj_str_t str_port;
695
696 str_port.ptr = pj_strchr(host_port, ':');
697 if (str_port.ptr != NULL) {
698 int iport;
699
700 host->ptr = host_port->ptr;
701 host->slen = (str_port.ptr - host->ptr);
702 str_port.ptr++;
703 str_port.slen = host_port->slen - host->slen - 1;
704 iport = (int)pj_strtoul(&str_port);
705 if (iport < 1 || iport > 65535)
706 return PJ_EINVAL;
707 *port = (pj_uint16_t)iport;
708 } else {
709 *host = *host_port;
710 *port = 0;
711 }
712
713 return PJ_SUCCESS;
714}
715
716/* Create ICE media transports (when ice is enabled) */
717static pj_status_t create_ice_media_transports(void)
718{
719 char stunip[PJ_INET6_ADDRSTRLEN];
720 pj_ice_strans_cfg ice_cfg;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000721 unsigned i;
722 pj_status_t status;
723
Benny Prijonoda9785b2007-04-02 20:43:06 +0000724 /* Make sure STUN server resolution has completed */
725 status = pjsua_resolve_stun_server(PJ_TRUE);
726 if (status != PJ_SUCCESS) {
727 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
728 return status;
729 }
730
Benny Prijonof76e1392008-06-06 14:51:48 +0000731 /* Create ICE stream transport configuration */
732 pj_ice_strans_cfg_default(&ice_cfg);
733 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
734 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
735 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
736
737 ice_cfg.af = pj_AF_INET();
738 ice_cfg.resolver = pjsua_var.resolver;
739
740 /* Configure STUN settings */
741 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
742 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
743 ice_cfg.stun.server = pj_str(stunip);
744 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
745 }
746 ice_cfg.stun.no_host_cands = pjsua_var.media_cfg.ice_no_host_cands;
747
748 /* Configure TURN settings */
749 if (pjsua_var.media_cfg.enable_turn) {
750 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
751 &ice_cfg.turn.server,
752 &ice_cfg.turn.port);
753 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
754 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
755 return PJ_EINVAL;
756 }
757 if (ice_cfg.turn.port == 0)
758 ice_cfg.turn.port = 3479;
759 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
760 pj_memcpy(&ice_cfg.turn.auth_cred,
761 &pjsua_var.media_cfg.turn_auth_cred,
762 sizeof(ice_cfg.turn.auth_cred));
763 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000764
Benny Prijonoc97608e2007-03-23 16:34:20 +0000765 /* Create each media transport */
766 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono096c56c2007-09-15 08:30:16 +0000767 pjmedia_ice_cb ice_cb;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000768 char name[32];
Benny Prijonof76e1392008-06-06 14:51:48 +0000769 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000770
Benny Prijono096c56c2007-09-15 08:30:16 +0000771 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
772 ice_cb.on_ice_complete = &on_ice_complete;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000773 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i);
Benny Prijono224b4e22008-06-19 14:10:28 +0000774 pjsua_var.calls[i].med_tp_ready = PJ_EPENDING;
Benny Prijonof76e1392008-06-06 14:51:48 +0000775
776 comp_cnt = 1;
Benny Prijono551af422008-08-07 09:55:52 +0000777 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
Benny Prijonof76e1392008-06-06 14:51:48 +0000778 ++comp_cnt;
779
780 status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
781 &ice_cfg, &ice_cb,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000782 &pjsua_var.calls[i].med_tp);
783 if (status != PJ_SUCCESS) {
784 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
785 status);
786 goto on_error;
787 }
788
Benny Prijonof76e1392008-06-06 14:51:48 +0000789 /* Wait until transport is initialized, or time out */
790 PJSUA_UNLOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000791 while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000792 pjsua_handle_events(100);
793 }
794 PJSUA_LOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000795 if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000796 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
Benny Prijono224b4e22008-06-19 14:10:28 +0000797 pjsua_var.calls[i].med_tp_ready);
798 status = pjsua_var.calls[i].med_tp_ready;
Benny Prijonof76e1392008-06-06 14:51:48 +0000799 goto on_error;
800 }
801
Benny Prijonod8179652008-01-23 20:39:07 +0000802 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
803 PJMEDIA_DIR_ENCODING,
804 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono11da9bc2007-09-15 08:55:00 +0000805
Benny Prijonod8179652008-01-23 20:39:07 +0000806 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
807 PJMEDIA_DIR_DECODING,
808 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000809 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000810
811 return PJ_SUCCESS;
812
813on_error:
814 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
815 if (pjsua_var.calls[i].med_tp != NULL) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000816 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000817 pjsua_var.calls[i].med_tp = NULL;
818 }
819 }
820
Benny Prijonoc97608e2007-03-23 16:34:20 +0000821 return status;
822}
823
824
825/*
826 * Create UDP media transports for all the calls. This function creates
827 * one UDP media transport for each call.
828 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +0000829PJ_DEF(pj_status_t) pjsua_media_transports_create(
830 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000831{
832 pjsua_transport_config cfg;
833 unsigned i;
834 pj_status_t status;
835
836
837 /* Make sure pjsua_init() has been called */
838 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
839
840 PJSUA_LOCK();
841
842 /* Delete existing media transports */
843 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono40860c32008-09-04 13:55:33 +0000844 if (pjsua_var.calls[i].med_tp != NULL &&
845 pjsua_var.calls[i].med_tp_auto_del)
846 {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000847 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
848 pjsua_var.calls[i].med_tp = NULL;
849 }
850 }
851
852 /* Copy config */
853 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
854
Benny Prijono40860c32008-09-04 13:55:33 +0000855 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000856 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000857 status = create_ice_media_transports();
Benny Prijonoc97608e2007-03-23 16:34:20 +0000858 } else {
859 status = create_udp_media_transports(&cfg);
860 }
861
Benny Prijono40860c32008-09-04 13:55:33 +0000862 /* Set media transport auto_delete to True */
863 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
864 pjsua_var.calls[i].med_tp_auto_del = PJ_TRUE;
865 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000866
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000867 PJSUA_UNLOCK();
868
869 return status;
870}
871
Benny Prijono40860c32008-09-04 13:55:33 +0000872/*
873 * Attach application's created media transports.
874 */
875PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
876 unsigned count,
877 pj_bool_t auto_delete)
878{
879 unsigned i;
880
881 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
882
883 /* Assign the media transports */
884 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
885 if (pjsua_var.calls[i].med_tp != NULL &&
886 pjsua_var.calls[i].med_tp_auto_del)
887 {
888 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
889 }
890
891 pjsua_var.calls[i].med_tp = tp[i].transport;
892 pjsua_var.calls[i].med_tp_auto_del = auto_delete;
893 }
894
895 return PJ_SUCCESS;
896}
897
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000898
Benny Prijonoc97608e2007-03-23 16:34:20 +0000899pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
Benny Prijonod8179652008-01-23 20:39:07 +0000900 pjsip_role_e role,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000901 int security_level,
Benny Prijono224b4e22008-06-19 14:10:28 +0000902 pj_pool_t *tmp_pool,
903 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000904 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000905{
906 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono224b4e22008-06-19 14:10:28 +0000907 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000908
Benny Prijonod8179652008-01-23 20:39:07 +0000909#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
910 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
911 pjmedia_srtp_setting srtp_opt;
Benny Prijonoa310bd22008-06-27 21:19:44 +0000912 pjmedia_transport *srtp = NULL;
Benny Prijonod8179652008-01-23 20:39:07 +0000913#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000914
Benny Prijonod8179652008-01-23 20:39:07 +0000915 PJ_UNUSED_ARG(role);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000916
Benny Prijonod8179652008-01-23 20:39:07 +0000917 /* Return error if media transport has not been created yet
918 * (e.g. application is starting)
919 */
920 if (call->med_tp == NULL) {
Benny Prijono03789052008-09-16 14:30:50 +0000921 if (sip_err_code)
922 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
Benny Prijonod8179652008-01-23 20:39:07 +0000923 return PJ_EBUSY;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000924 }
925
Benny Prijonod8179652008-01-23 20:39:07 +0000926#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Benny Prijono53a7c702008-04-14 02:57:29 +0000927 /* This function may be called when SRTP transport already exists
928 * (e.g: in re-invite, update), don't need to destroy/re-create.
929 */
930 if (!call->med_orig || call->med_tp == call->med_orig) {
931
932 /* Check if SRTP requires secure signaling */
933 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
934 if (security_level < acc->cfg.srtp_secure_signaling) {
935 if (sip_err_code)
936 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
937 return PJSIP_ESESSIONINSECURE;
938 }
Benny Prijonod8179652008-01-23 20:39:07 +0000939 }
Benny Prijonod8179652008-01-23 20:39:07 +0000940
Benny Prijono53a7c702008-04-14 02:57:29 +0000941 /* Always create SRTP adapter */
942 pjmedia_srtp_setting_default(&srtp_opt);
943 srtp_opt.close_member_tp = PJ_FALSE;
Nanang Izzuddin4375f902008-06-26 19:12:09 +0000944 /* If media session has been ever established, let's use remote's
945 * preference in SRTP usage policy, especially when it is stricter.
946 */
947 if (call->rem_srtp_use > acc->cfg.use_srtp)
948 srtp_opt.use = call->rem_srtp_use;
949 else
950 srtp_opt.use = acc->cfg.use_srtp;
951
Benny Prijono53a7c702008-04-14 02:57:29 +0000952 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
953 call->med_tp,
954 &srtp_opt, &srtp);
955 if (status != PJ_SUCCESS) {
956 if (sip_err_code)
957 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
958 return status;
959 }
Benny Prijonod8179652008-01-23 20:39:07 +0000960
Benny Prijono53a7c702008-04-14 02:57:29 +0000961 /* Set SRTP as current media transport */
962 call->med_orig = call->med_tp;
963 call->med_tp = srtp;
964 }
Benny Prijonod8179652008-01-23 20:39:07 +0000965#else
966 call->med_orig = call->med_tp;
967 PJ_UNUSED_ARG(security_level);
968#endif
969
Benny Prijonoa310bd22008-06-27 21:19:44 +0000970 /* Find out which media line in SDP that we support. If we are offerer,
971 * audio will be at index 0 in SDP.
972 */
973 if (rem_sdp == 0) {
974 call->audio_idx = 0;
975 }
976 /* Otherwise find out the candidate audio media line in SDP */
977 else {
978 unsigned i;
979 pj_bool_t srtp_active;
980
981#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
982 srtp_active = acc->cfg.use_srtp && srtp != NULL;
983#else
984 srtp_active = PJ_FALSE;
985#endif
986
987 /* Media count must have been checked */
988 pj_assert(rem_sdp->media_count != 0);
989
990 for (i=0; i<rem_sdp->media_count; ++i) {
991 const pjmedia_sdp_media *m = rem_sdp->media[i];
992
993 /* Skip if media is not audio */
994 if (pj_stricmp2(&m->desc.media, "audio") != 0)
995 continue;
996
997 /* Skip if media is disabled */
998 if (m->desc.port == 0)
999 continue;
1000
1001 /* Skip if transport is not supported */
1002 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") != 0 &&
1003 pj_stricmp2(&m->desc.transport, "RTP/SAVP") != 0)
1004 {
1005 continue;
1006 }
1007
1008 if (call->audio_idx == -1) {
1009 call->audio_idx = i;
1010 } else {
1011 /* We've found multiple candidates. This could happen
1012 * e.g. when remote is offering both RTP/AVP and RTP/AVP,
1013 * or when remote for some reason offers two audio.
1014 */
1015
1016 if (srtp_active &&
1017 pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0)
1018 {
1019 /* Prefer RTP/SAVP when our media transport is SRTP */
1020 call->audio_idx = i;
1021 } else if (!srtp_active &&
1022 pj_stricmp2(&m->desc.transport, "RTP/AVP")==0)
1023 {
1024 /* Prefer RTP/AVP when our media transport is NOT SRTP */
1025 call->audio_idx = i;
1026 }
1027 }
1028 }
1029 }
1030
1031 /* Reject offer if we couldn't find a good m=audio line in offer */
1032 if (call->audio_idx < 0) {
Benny Prijonoab8dba92008-06-27 21:59:15 +00001033 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001034 pjsua_media_channel_deinit(call_id);
Benny Prijonoab8dba92008-06-27 21:59:15 +00001035 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001036 }
1037
1038 PJ_LOG(4,(THIS_FILE, "Media index %d selected for call %d",
1039 call->audio_idx, call->index));
1040
Benny Prijono224b4e22008-06-19 14:10:28 +00001041 /* Create the media transport */
1042 status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001043 rem_sdp, call->audio_idx);
Benny Prijono224b4e22008-06-19 14:10:28 +00001044 if (status != PJ_SUCCESS) {
1045 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1046 pjsua_media_channel_deinit(call_id);
1047 return status;
1048 }
1049
1050 call->med_tp_st = PJSUA_MED_TP_INIT;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001051 return PJ_SUCCESS;
1052}
1053
1054pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1055 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001056 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001057 pjmedia_sdp_session **p_sdp,
1058 int *sip_status_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001059{
Benny Prijonoa310bd22008-06-27 21:19:44 +00001060 enum { MAX_MEDIA = 1 };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001061 pjmedia_sdp_session *sdp;
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001062 pjmedia_transport_info tpinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001063 pjsua_call *call = &pjsua_var.calls[call_id];
1064 pj_status_t status;
1065
Benny Prijono55e82352007-05-10 20:49:08 +00001066 /* Return error if media transport has not been created yet
1067 * (e.g. application is starting)
1068 */
1069 if (call->med_tp == NULL) {
1070 return PJ_EBUSY;
1071 }
1072
Benny Prijonoa310bd22008-06-27 21:19:44 +00001073 /* Media index must have been determined before */
1074 pj_assert(call->audio_idx != -1);
1075
Benny Prijono224b4e22008-06-19 14:10:28 +00001076 /* Create media if it's not created. This could happen when call is
1077 * currently on-hold
1078 */
1079 if (call->med_tp_st == PJSUA_MED_TP_IDLE) {
1080 pjsip_role_e role;
1081 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1082 status = pjsua_media_channel_init(call_id, role, call->secure_level,
1083 pool, rem_sdp, sip_status_code);
1084 if (status != PJ_SUCCESS)
1085 return status;
1086 }
1087
Benny Prijono617c5bc2007-04-02 19:51:21 +00001088 /* Get media socket info */
Benny Prijono734fc2d2008-03-17 16:05:35 +00001089 pjmedia_transport_info_init(&tpinfo);
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001090 pjmedia_transport_get_info(call->med_tp, &tpinfo);
Benny Prijono617c5bc2007-04-02 19:51:21 +00001091
1092 /* Create SDP */
Benny Prijonod8179652008-01-23 20:39:07 +00001093 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA,
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001094 &tpinfo.sock_info, &sdp);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001095 if (status != PJ_SUCCESS) {
1096 if (sip_status_code) *sip_status_code = 500;
Benny Prijono224b4e22008-06-19 14:10:28 +00001097 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001098 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001099
Benny Prijonoa310bd22008-06-27 21:19:44 +00001100 /* If we're answering and the selected media is not the first media
1101 * in SDP, then fill in the unselected media with with zero port.
1102 * Otherwise we'll crash in transport_encode_sdp() because the media
1103 * lines are not aligned between offer and answer.
1104 */
1105 if (rem_sdp && call->audio_idx != 0) {
1106 unsigned i;
1107
1108 for (i=0; i<rem_sdp->media_count; ++i) {
1109 const pjmedia_sdp_media *rem_m = rem_sdp->media[i];
1110 pjmedia_sdp_media *m;
1111 const pjmedia_sdp_attr *a;
1112
1113 if ((int)i == call->audio_idx)
1114 continue;
1115
1116 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1117 pj_strdup(pool, &m->desc.media, &rem_m->desc.media);
1118 pj_strdup(pool, &m->desc.transport, &rem_m->desc.transport);
1119 m->desc.port = 0;
1120
1121 /* Add one format, copy from the offer. And copy the corresponding
1122 * rtpmap and fmtp attributes too.
1123 */
1124 m->desc.fmt_count = 1;
1125 pj_strdup(pool, &m->desc.fmt[0], &rem_m->desc.fmt[0]);
1126 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1127 "rtpmap", &m->desc.fmt[0])) != NULL)
1128 {
1129 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1130 }
1131 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1132 "fmtp", &m->desc.fmt[0])) != NULL)
1133 {
1134 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1135 }
1136
1137 if (i==sdp->media_count)
1138 sdp->media[sdp->media_count++] = m;
1139 else {
1140 pj_array_insert(sdp->media, sizeof(sdp->media[0]),
1141 sdp->media_count, i, &m);
1142 ++sdp->media_count;
1143 }
1144 }
1145 }
1146
Benny Prijono6ba8c542007-10-16 01:34:14 +00001147 /* Add NAT info in the SDP */
1148 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1149 pjmedia_sdp_attr *a;
1150 pj_str_t value;
1151 char nat_info[80];
1152
1153 value.ptr = nat_info;
1154 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1155 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1156 "%d", pjsua_var.nat_type);
1157 } else {
1158 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1159 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1160 "%d %s",
1161 pjsua_var.nat_type,
1162 type_name);
1163 }
1164
1165 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1166
1167 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1168
1169 }
1170
Benny Prijonod8179652008-01-23 20:39:07 +00001171 /* Give the SDP to media transport */
Benny Prijono224b4e22008-06-19 14:10:28 +00001172 status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001173 call->audio_idx);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001174 if (status != PJ_SUCCESS) {
1175 if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono224b4e22008-06-19 14:10:28 +00001176 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001177 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001178
1179 *p_sdp = sdp;
1180 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001181}
1182
1183
1184static void stop_media_session(pjsua_call_id call_id)
1185{
1186 pjsua_call *call = &pjsua_var.calls[call_id];
1187
1188 if (call->conf_slot != PJSUA_INVALID_ID) {
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001189 if (pjsua_var.mconf) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001190 pjsua_conf_remove_port(call->conf_slot);
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001191 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001192 call->conf_slot = PJSUA_INVALID_ID;
1193 }
1194
1195 if (call->session) {
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001196 pjmedia_rtcp_stat stat;
1197
Nanang Izzuddin437d77c2008-08-26 18:04:15 +00001198 if (pjmedia_session_get_stream_stat(call->session, 0, &stat)
1199 == PJ_SUCCESS)
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001200 {
1201 /* Save RTP timestamp & sequence, so when media session is
1202 * restarted, those values will be restored as the initial
1203 * RTP timestamp & sequence of the new media session. So in
1204 * the same call session, RTP timestamp and sequence are
1205 * guaranteed to be contigue.
1206 */
1207 call->rtp_tx_seq_ts_set = 1 | (1 << 1);
1208 call->rtp_tx_seq = stat.rtp_tx_last_seq;
1209 call->rtp_tx_ts = stat.rtp_tx_last_ts;
1210 }
1211
Benny Prijonofc13bf62008-02-20 08:56:15 +00001212 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1213 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0);
1214 }
1215
Benny Prijonoc97608e2007-03-23 16:34:20 +00001216 pjmedia_session_destroy(call->session);
1217 call->session = NULL;
1218
1219 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
1220 call_id));
1221
1222 }
1223
1224 call->media_st = PJSUA_CALL_MEDIA_NONE;
1225}
1226
1227pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1228{
1229 pjsua_call *call = &pjsua_var.calls[call_id];
1230
1231 stop_media_session(call_id);
1232
Benny Prijono224b4e22008-06-19 14:10:28 +00001233 if (call->med_tp_st != PJSUA_MED_TP_IDLE) {
1234 pjmedia_transport_media_stop(call->med_tp);
1235 call->med_tp_st = PJSUA_MED_TP_IDLE;
1236 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001237
Benny Prijono311b63f2008-07-14 11:31:40 +00001238 if (call->med_orig && call->med_tp && call->med_tp != call->med_orig) {
Benny Prijonod8179652008-01-23 20:39:07 +00001239 pjmedia_transport_close(call->med_tp);
1240 call->med_tp = call->med_orig;
1241 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001242 return PJ_SUCCESS;
1243}
1244
1245
1246/*
1247 * DTMF callback from the stream.
1248 */
1249static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1250 int digit)
1251{
1252 PJ_UNUSED_ARG(strm);
1253
Benny Prijono0c068262008-02-14 14:38:52 +00001254 /* For discussions about call mutex protection related to this
1255 * callback, please see ticket #460:
1256 * http://trac.pjsip.org/repos/ticket/460#comment:4
1257 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001258 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1259 pjsua_call_id call_id;
1260
Benny Prijonod8179652008-01-23 20:39:07 +00001261 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001262 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1263 }
1264}
1265
1266
1267pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijono224b4e22008-06-19 14:10:28 +00001268 const pjmedia_sdp_session *local_sdp,
Benny Prijonodbce2cf2007-03-28 16:24:00 +00001269 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001270{
1271 int prev_media_st = 0;
1272 pjsua_call *call = &pjsua_var.calls[call_id];
1273 pjmedia_session_info sess_info;
Benny Prijono91e567e2007-12-28 08:51:58 +00001274 pjmedia_stream_info *si = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001275 pjmedia_port *media_port;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001276 pj_status_t status;
1277
1278 /* Destroy existing media session, if any. */
1279 prev_media_st = call->media_st;
1280 stop_media_session(call->index);
1281
1282 /* Create media session info based on SDP parameters.
Benny Prijonoc97608e2007-03-23 16:34:20 +00001283 */
1284 status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
1285 pjsua_var.med_endpt,
Benny Prijono91e567e2007-12-28 08:51:58 +00001286 PJMEDIA_MAX_SDP_MEDIA, &sess_info,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001287 local_sdp, remote_sdp);
1288 if (status != PJ_SUCCESS)
1289 return status;
1290
Benny Prijonoa310bd22008-06-27 21:19:44 +00001291 /* Find which session is audio */
1292 PJ_ASSERT_RETURN(call->audio_idx != -1, PJ_EBUG);
1293 PJ_ASSERT_RETURN(call->audio_idx < (int)sess_info.stream_cnt, PJ_EBUG);
1294 si = &sess_info.stream_info[call->audio_idx];
Benny Prijono91e567e2007-12-28 08:51:58 +00001295
1296 /* Reset session info with only one media stream */
1297 sess_info.stream_cnt = 1;
1298 if (si != &sess_info.stream_info[0])
1299 pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001300
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001301 /* Check if no media is active */
Benny Prijono91e567e2007-12-28 08:51:58 +00001302 if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001303 {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001304 /* Call media state */
1305 call->media_st = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001306
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001307 /* Call media direction */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001308 call->media_dir = PJMEDIA_DIR_NONE;
1309
Benny Prijonod8179652008-01-23 20:39:07 +00001310 /* Shutdown transport's session */
1311 pjmedia_transport_media_stop(call->med_tp);
Benny Prijono224b4e22008-06-19 14:10:28 +00001312 call->med_tp_st = PJSUA_MED_TP_IDLE;
Benny Prijono667952e2007-04-02 19:27:54 +00001313
Benny Prijonoc97608e2007-03-23 16:34:20 +00001314 /* No need because we need keepalive? */
1315
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001316 /* Close upper entry of transport stack */
1317 if (call->med_orig && (call->med_tp != call->med_orig)) {
1318 pjmedia_transport_close(call->med_tp);
1319 call->med_tp = call->med_orig;
1320 }
1321
Benny Prijonoc97608e2007-03-23 16:34:20 +00001322 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001323 pjmedia_transport_info tp_info;
1324
Benny Prijono224b4e22008-06-19 14:10:28 +00001325 /* Start/restart media transport */
Benny Prijonod8179652008-01-23 20:39:07 +00001326 status = pjmedia_transport_media_start(call->med_tp,
1327 call->inv->pool,
1328 local_sdp, remote_sdp, 0);
1329 if (status != PJ_SUCCESS)
1330 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001331
Benny Prijono224b4e22008-06-19 14:10:28 +00001332 call->med_tp_st = PJSUA_MED_TP_RUNNING;
1333
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001334 /* Get remote SRTP usage policy */
1335 pjmedia_transport_info_init(&tp_info);
1336 pjmedia_transport_get_info(call->med_tp, &tp_info);
1337 if (tp_info.specific_info_cnt > 0) {
1338 int i;
1339 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1340 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1341 {
1342 pjmedia_srtp_info *srtp_info =
1343 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1344
1345 call->rem_srtp_use = srtp_info->peer_use;
1346 break;
1347 }
1348 }
1349 }
1350
Benny Prijonoc97608e2007-03-23 16:34:20 +00001351 /* Override ptime, if this option is specified. */
1352 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001353 si->param->setting.frm_per_pkt = (pj_uint8_t)
1354 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1355 if (si->param->setting.frm_per_pkt == 0)
1356 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001357 }
1358
1359 /* Disable VAD, if this option is specified. */
1360 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001361 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001362 }
1363
1364
1365 /* Optionally, application may modify other stream settings here
1366 * (such as jitter buffer parameters, codec ptime, etc.)
1367 */
Benny Prijono91e567e2007-12-28 08:51:58 +00001368 si->jb_init = pjsua_var.media_cfg.jb_init;
1369 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1370 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1371 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001372
Benny Prijono8147f402007-11-21 14:50:07 +00001373 /* Set SSRC */
Benny Prijono91e567e2007-12-28 08:51:58 +00001374 si->ssrc = call->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00001375
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001376 /* Set RTP timestamp & sequence, normally these value are intialized
1377 * automatically when stream session created, but for some cases (e.g:
1378 * call reinvite, call update) timestamp and sequence need to be kept
1379 * contigue.
1380 */
1381 si->rtp_ts = call->rtp_tx_ts;
1382 si->rtp_seq = call->rtp_tx_seq;
1383 si->rtp_seq_ts_set = call->rtp_tx_seq_ts_set;
1384
Benny Prijonoc97608e2007-03-23 16:34:20 +00001385 /* Create session based on session info. */
1386 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
1387 &call->med_tp,
1388 call, &call->session );
1389 if (status != PJ_SUCCESS) {
1390 return status;
1391 }
1392
1393 /* If DTMF callback is installed by application, install our
1394 * callback to the session.
1395 */
1396 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1397 pjmedia_session_set_dtmf_callback(call->session, 0,
1398 &dtmf_callback,
Benny Prijonod8179652008-01-23 20:39:07 +00001399 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001400 }
1401
1402 /* Get the port interface of the first stream in the session.
1403 * We need the port interface to add to the conference bridge.
1404 */
1405 pjmedia_session_get_port(call->session, 0, &media_port);
1406
Benny Prijonofc13bf62008-02-20 08:56:15 +00001407 /* Notify application about stream creation.
1408 * Note: application may modify media_port to point to different
1409 * media port
1410 */
1411 if (pjsua_var.ua_cfg.cb.on_stream_created) {
1412 pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session,
1413 0, &media_port);
1414 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001415
1416 /*
1417 * Add the call to conference bridge.
1418 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001419 {
1420 char tmp[PJSIP_MAX_URL_SIZE];
1421 pj_str_t port_name;
1422
1423 port_name.ptr = tmp;
1424 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1425 call->inv->dlg->remote.info->uri,
1426 tmp, sizeof(tmp));
1427 if (port_name.slen < 1) {
1428 port_name = pj_str("call");
1429 }
1430 status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
1431 media_port,
1432 &port_name,
1433 (unsigned*)&call->conf_slot);
1434 if (status != PJ_SUCCESS) {
1435 return status;
1436 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001437 }
1438
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001439 /* Call media direction */
Benny Prijono91e567e2007-12-28 08:51:58 +00001440 call->media_dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001441
1442 /* Call media state */
1443 if (call->local_hold)
1444 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
1445 else if (call->media_dir == PJMEDIA_DIR_DECODING)
1446 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
1447 else
1448 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001449 }
1450
1451 /* Print info. */
1452 {
1453 char info[80];
1454 int info_len = 0;
1455 unsigned i;
1456
1457 for (i=0; i<sess_info.stream_cnt; ++i) {
1458 int len;
1459 const char *dir;
1460 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1461
1462 switch (strm_info->dir) {
1463 case PJMEDIA_DIR_NONE:
1464 dir = "inactive";
1465 break;
1466 case PJMEDIA_DIR_ENCODING:
1467 dir = "sendonly";
1468 break;
1469 case PJMEDIA_DIR_DECODING:
1470 dir = "recvonly";
1471 break;
1472 case PJMEDIA_DIR_ENCODING_DECODING:
1473 dir = "sendrecv";
1474 break;
1475 default:
1476 dir = "unknown";
1477 break;
1478 }
1479 len = pj_ansi_sprintf( info+info_len,
1480 ", stream #%d: %.*s (%s)", i,
1481 (int)strm_info->fmt.encoding_name.slen,
1482 strm_info->fmt.encoding_name.ptr,
1483 dir);
1484 if (len > 0)
1485 info_len += len;
1486 }
1487 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1488 }
1489
1490 return PJ_SUCCESS;
1491}
1492
1493
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001494/*
1495 * Get maxinum number of conference ports.
1496 */
1497PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1498{
1499 return pjsua_var.media_cfg.max_media_ports;
1500}
1501
1502
1503/*
1504 * Get current number of active ports in the bridge.
1505 */
1506PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1507{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001508 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001509 unsigned count = PJ_ARRAY_SIZE(ports);
1510 pj_status_t status;
1511
1512 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1513 if (status != PJ_SUCCESS)
1514 count = 0;
1515
1516 return count;
1517}
1518
1519
1520/*
1521 * Enumerate all conference ports.
1522 */
1523PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1524 unsigned *count)
1525{
1526 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1527}
1528
1529
1530/*
1531 * Get information about the specified conference port
1532 */
1533PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1534 pjsua_conf_port_info *info)
1535{
1536 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001537 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001538 pj_status_t status;
1539
1540 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1541 if (status != PJ_SUCCESS)
1542 return status;
1543
Benny Prijonoac623b32006-07-03 15:19:31 +00001544 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001545 info->slot_id = id;
1546 info->name = cinfo.name;
1547 info->clock_rate = cinfo.clock_rate;
1548 info->channel_count = cinfo.channel_count;
1549 info->samples_per_frame = cinfo.samples_per_frame;
1550 info->bits_per_sample = cinfo.bits_per_sample;
1551
1552 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001553 info->listener_cnt = cinfo.listener_cnt;
1554 for (i=0; i<cinfo.listener_cnt; ++i) {
1555 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001556 }
1557
1558 return PJ_SUCCESS;
1559}
1560
1561
1562/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001563 * Add arbitrary media port to PJSUA's conference bridge.
1564 */
1565PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1566 pjmedia_port *port,
1567 pjsua_conf_port_id *p_id)
1568{
1569 pj_status_t status;
1570
1571 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1572 port, NULL, (unsigned*)p_id);
1573 if (status != PJ_SUCCESS) {
1574 if (p_id)
1575 *p_id = PJSUA_INVALID_ID;
1576 }
1577
1578 return status;
1579}
1580
1581
1582/*
1583 * Remove arbitrary slot from the conference bridge.
1584 */
1585PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1586{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001587 pj_status_t status;
1588
1589 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1590 check_snd_dev_idle();
1591
1592 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00001593}
1594
1595
1596/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001597 * Establish unidirectional media flow from souce to sink.
1598 */
1599PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1600 pjsua_conf_port_id sink)
1601{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001602 /* If sound device idle timer is active, cancel it first. */
1603 if (pjsua_var.snd_idle_timer.id) {
1604 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
1605 pjsua_var.snd_idle_timer.id = PJ_FALSE;
1606 }
1607
1608 /* Create sound port if none is instantiated */
1609 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1610 !pjsua_var.no_snd)
1611 {
1612 pj_status_t status;
1613
1614 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1615 if (status != PJ_SUCCESS) {
1616 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1617 return status;
1618 }
1619 }
1620
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001621 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
1622}
1623
1624
1625/*
1626 * Disconnect media flow from the source to destination port.
1627 */
1628PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
1629 pjsua_conf_port_id sink)
1630{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001631 pj_status_t status;
1632
1633 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001634 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001635
1636 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001637}
1638
1639
Benny Prijono6dd967c2006-12-26 02:27:14 +00001640/*
1641 * Adjust the signal level to be transmitted from the bridge to the
1642 * specified port by making it louder or quieter.
1643 */
1644PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
1645 float level)
1646{
1647 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
1648 (int)((level-1) * 128));
1649}
1650
1651/*
1652 * Adjust the signal level to be received from the specified port (to
1653 * the bridge) by making it louder or quieter.
1654 */
1655PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
1656 float level)
1657{
1658 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
1659 (int)((level-1) * 128));
1660}
1661
1662
1663/*
1664 * Get last signal level transmitted to or received from the specified port.
1665 */
1666PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
1667 unsigned *tx_level,
1668 unsigned *rx_level)
1669{
1670 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
1671 tx_level, rx_level);
1672}
1673
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001674/*****************************************************************************
1675 * File player.
1676 */
1677
Benny Prijonod5696da2007-07-17 16:25:45 +00001678static char* get_basename(const char *path, unsigned len)
1679{
1680 char *p = ((char*)path) + len;
1681
1682 if (len==0)
1683 return p;
1684
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001685 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00001686
1687 return (p==path) ? p : p+1;
1688}
1689
1690
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001691/*
1692 * Create a file player, and automatically connect this player to
1693 * the conference bridge.
1694 */
1695PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1696 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001697 pjsua_player_id *p_id)
1698{
1699 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001700 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00001701 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001702 pjmedia_port *port;
1703 pj_status_t status;
1704
1705 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1706 return PJ_ETOOMANY;
1707
1708 PJSUA_LOCK();
1709
1710 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1711 if (pjsua_var.player[file_id].port == NULL)
1712 break;
1713 }
1714
1715 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1716 /* This is unexpected */
1717 PJSUA_UNLOCK();
1718 pj_assert(0);
1719 return PJ_EBUG;
1720 }
1721
1722 pj_memcpy(path, filename->ptr, filename->slen);
1723 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00001724
1725 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1726 if (!pool) {
1727 PJSUA_UNLOCK();
1728 return PJ_ENOMEM;
1729 }
1730
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00001731 status = pjmedia_wav_player_port_create(
1732 pool, path,
1733 pjsua_var.mconf_cfg.samples_per_frame *
1734 1000 / pjsua_var.media_cfg.channel_count /
1735 pjsua_var.media_cfg.clock_rate,
1736 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001737 if (status != PJ_SUCCESS) {
1738 PJSUA_UNLOCK();
1739 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001740 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001741 return status;
1742 }
1743
Benny Prijono5297af92008-03-18 13:40:40 +00001744 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001745 port, filename, &slot);
1746 if (status != PJ_SUCCESS) {
1747 pjmedia_port_destroy(port);
1748 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00001749 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1750 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001751 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001752 return status;
1753 }
1754
Benny Prijonoa66c3312007-01-21 23:12:40 +00001755 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00001756 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001757 pjsua_var.player[file_id].port = port;
1758 pjsua_var.player[file_id].slot = slot;
1759
1760 if (p_id) *p_id = file_id;
1761
1762 ++pjsua_var.player_cnt;
1763
1764 PJSUA_UNLOCK();
1765 return PJ_SUCCESS;
1766}
1767
1768
1769/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00001770 * Create a file playlist media port, and automatically add the port
1771 * to the conference bridge.
1772 */
1773PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
1774 unsigned file_count,
1775 const pj_str_t *label,
1776 unsigned options,
1777 pjsua_player_id *p_id)
1778{
1779 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00001780 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001781 pjmedia_port *port;
1782 pj_status_t status;
1783
1784 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1785 return PJ_ETOOMANY;
1786
1787 PJSUA_LOCK();
1788
1789 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1790 if (pjsua_var.player[file_id].port == NULL)
1791 break;
1792 }
1793
1794 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1795 /* This is unexpected */
1796 PJSUA_UNLOCK();
1797 pj_assert(0);
1798 return PJ_EBUG;
1799 }
1800
1801
1802 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
1803 pjsua_var.media_cfg.clock_rate;
1804
Benny Prijonod5696da2007-07-17 16:25:45 +00001805 pool = pjsua_pool_create("playlist", 1000, 1000);
1806 if (!pool) {
1807 PJSUA_UNLOCK();
1808 return PJ_ENOMEM;
1809 }
1810
1811 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001812 file_names, file_count,
1813 ptime, options, 0, &port);
1814 if (status != PJ_SUCCESS) {
1815 PJSUA_UNLOCK();
1816 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001817 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001818 return status;
1819 }
1820
Benny Prijonod5696da2007-07-17 16:25:45 +00001821 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001822 port, &port->info.name, &slot);
1823 if (status != PJ_SUCCESS) {
1824 pjmedia_port_destroy(port);
1825 PJSUA_UNLOCK();
1826 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001827 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001828 return status;
1829 }
1830
1831 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00001832 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001833 pjsua_var.player[file_id].port = port;
1834 pjsua_var.player[file_id].slot = slot;
1835
1836 if (p_id) *p_id = file_id;
1837
1838 ++pjsua_var.player_cnt;
1839
1840 PJSUA_UNLOCK();
1841 return PJ_SUCCESS;
1842
1843}
1844
1845
1846/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001847 * Get conference port ID associated with player.
1848 */
1849PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
1850{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001851 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001852 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1853
1854 return pjsua_var.player[id].slot;
1855}
1856
Benny Prijono469b1522006-12-26 03:05:17 +00001857/*
1858 * Get the media port for the player.
1859 */
Benny Prijonobe41d862008-01-18 13:24:28 +00001860PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00001861 pjmedia_port **p_port)
1862{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001863 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001864 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1865 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1866
1867 *p_port = pjsua_var.player[id].port;
1868
1869 return PJ_SUCCESS;
1870}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001871
1872/*
1873 * Set playback position.
1874 */
1875PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
1876 pj_uint32_t samples)
1877{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001878 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001879 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001880 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001881
1882 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
1883}
1884
1885
1886/*
1887 * Close the file, remove the player from the bridge, and free
1888 * resources associated with the file player.
1889 */
1890PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
1891{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001892 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001893 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1894
1895 PJSUA_LOCK();
1896
1897 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001898 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001899 pjmedia_port_destroy(pjsua_var.player[id].port);
1900 pjsua_var.player[id].port = NULL;
1901 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00001902 pj_pool_release(pjsua_var.player[id].pool);
1903 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001904 pjsua_var.player_cnt--;
1905 }
1906
1907 PJSUA_UNLOCK();
1908
1909 return PJ_SUCCESS;
1910}
1911
1912
1913/*****************************************************************************
1914 * File recorder.
1915 */
1916
1917/*
1918 * Create a file recorder, and automatically connect this recorder to
1919 * the conference bridge.
1920 */
1921PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00001922 unsigned enc_type,
1923 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001924 pj_ssize_t max_size,
1925 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001926 pjsua_recorder_id *p_id)
1927{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001928 enum Format
1929 {
1930 FMT_UNKNOWN,
1931 FMT_WAV,
1932 FMT_MP3,
1933 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001934 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001935 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001936 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00001937 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00001938 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001939 pjmedia_port *port;
1940 pj_status_t status;
1941
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001942 /* Filename must present */
1943 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
1944
Benny Prijono00cae612006-07-31 15:19:36 +00001945 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001946 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001947
Benny Prijono8f310522006-10-20 11:08:49 +00001948 /* Don't support encoding type at present */
1949 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001950
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001951 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
1952 return PJ_ETOOMANY;
1953
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001954 /* Determine the file format */
1955 ext.ptr = filename->ptr + filename->slen - 4;
1956 ext.slen = 4;
1957
1958 if (pj_stricmp2(&ext, ".wav") == 0)
1959 file_format = FMT_WAV;
1960 else if (pj_stricmp2(&ext, ".mp3") == 0)
1961 file_format = FMT_MP3;
1962 else {
1963 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
1964 "determine file format for %.*s",
1965 (int)filename->slen, filename->ptr));
1966 return PJ_ENOTSUP;
1967 }
1968
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001969 PJSUA_LOCK();
1970
1971 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
1972 if (pjsua_var.recorder[file_id].port == NULL)
1973 break;
1974 }
1975
1976 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
1977 /* This is unexpected */
1978 PJSUA_UNLOCK();
1979 pj_assert(0);
1980 return PJ_EBUG;
1981 }
1982
1983 pj_memcpy(path, filename->ptr, filename->slen);
1984 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001985
Benny Prijonod5696da2007-07-17 16:25:45 +00001986 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1987 if (!pool) {
1988 PJSUA_UNLOCK();
1989 return PJ_ENOMEM;
1990 }
1991
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001992 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00001993 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001994 pjsua_var.media_cfg.clock_rate,
1995 pjsua_var.mconf_cfg.channel_count,
1996 pjsua_var.mconf_cfg.samples_per_frame,
1997 pjsua_var.mconf_cfg.bits_per_sample,
1998 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001999 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00002000 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002001 port = NULL;
2002 status = PJ_ENOTSUP;
2003 }
2004
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002005 if (status != PJ_SUCCESS) {
2006 PJSUA_UNLOCK();
2007 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002008 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002009 return status;
2010 }
2011
Benny Prijonod5696da2007-07-17 16:25:45 +00002012 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002013 port, filename, &slot);
2014 if (status != PJ_SUCCESS) {
2015 pjmedia_port_destroy(port);
2016 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00002017 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002018 return status;
2019 }
2020
2021 pjsua_var.recorder[file_id].port = port;
2022 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00002023 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002024
2025 if (p_id) *p_id = file_id;
2026
2027 ++pjsua_var.rec_cnt;
2028
2029 PJSUA_UNLOCK();
2030 return PJ_SUCCESS;
2031}
2032
2033
2034/*
2035 * Get conference port associated with recorder.
2036 */
2037PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
2038{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002039 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2040 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002041 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2042
2043 return pjsua_var.recorder[id].slot;
2044}
2045
Benny Prijono469b1522006-12-26 03:05:17 +00002046/*
2047 * Get the media port for the recorder.
2048 */
2049PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
2050 pjmedia_port **p_port)
2051{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002052 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2053 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002054 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2055 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2056
2057 *p_port = pjsua_var.recorder[id].port;
2058 return PJ_SUCCESS;
2059}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002060
2061/*
2062 * Destroy recorder (this will complete recording).
2063 */
2064PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
2065{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002066 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2067 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002068 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2069
2070 PJSUA_LOCK();
2071
2072 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002073 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002074 pjmedia_port_destroy(pjsua_var.recorder[id].port);
2075 pjsua_var.recorder[id].port = NULL;
2076 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002077 pj_pool_release(pjsua_var.recorder[id].pool);
2078 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002079 pjsua_var.rec_cnt--;
2080 }
2081
2082 PJSUA_UNLOCK();
2083
2084 return PJ_SUCCESS;
2085}
2086
2087
2088/*****************************************************************************
2089 * Sound devices.
2090 */
2091
2092/*
2093 * Enum sound devices.
2094 */
2095PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
2096 unsigned *count)
2097{
2098 unsigned i, dev_count;
2099
2100 dev_count = pjmedia_snd_get_dev_count();
2101
2102 if (dev_count > *count) dev_count = *count;
2103
2104 for (i=0; i<dev_count; ++i) {
2105 const pjmedia_snd_dev_info *ci;
2106
2107 ci = pjmedia_snd_get_dev_info(i);
2108 pj_memcpy(&info[i], ci, sizeof(*ci));
2109 }
2110
2111 *count = dev_count;
2112
2113 return PJ_SUCCESS;
2114}
2115
2116
2117/* Close existing sound device */
2118static void close_snd_dev(void)
2119{
2120 /* Close sound device */
2121 if (pjsua_var.snd_port) {
2122 const pjmedia_snd_dev_info *cap_info, *play_info;
2123
2124 cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
2125 play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
2126
2127 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
2128 "%s sound capture device",
2129 play_info->name, cap_info->name));
2130
2131 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
2132 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2133 pjsua_var.snd_port = NULL;
2134 }
2135
2136 /* Close null sound device */
2137 if (pjsua_var.null_snd) {
2138 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
2139 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
2140 pjsua_var.null_snd = NULL;
2141 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002142
2143 if (pjsua_var.snd_pool)
2144 pj_pool_release(pjsua_var.snd_pool);
2145 pjsua_var.snd_pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002146}
2147
2148/*
2149 * Select or change sound device. Application may call this function at
2150 * any time to replace current sound device.
2151 */
2152PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
2153 int playback_dev)
2154{
2155 pjmedia_port *conf_port;
Benny Prijono6dd967c2006-12-26 02:27:14 +00002156 const pjmedia_snd_dev_info *play_info;
Benny Prijonof3758ee2008-02-26 15:32:16 +00002157 unsigned clock_rates[] = {0, 22050, 44100, 48000, 32000, 16000,
2158 8000};
Benny Prijono658a1c52006-10-11 21:56:16 +00002159 unsigned selected_clock_rate = 0;
Benny Prijono26056d82006-10-11 16:03:41 +00002160 unsigned i;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002161 pjmedia_snd_stream *strm;
2162 pjmedia_snd_stream_info si;
2163 pj_str_t tmp;
Benny Prijono26056d82006-10-11 16:03:41 +00002164 pj_status_t status = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002165
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002166 /* Check if NULL sound device is used */
2167 if (NULL_SND_DEV_ID == capture_dev || NULL_SND_DEV_ID == playback_dev) {
2168 return pjsua_set_null_snd_dev();
2169 }
2170
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002171 /* Close existing sound port */
2172 close_snd_dev();
2173
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002174 /* Create memory pool for sound device. */
2175 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2176 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002177
Benny Prijono26056d82006-10-11 16:03:41 +00002178 /* Set default clock rate */
Benny Prijonof3758ee2008-02-26 15:32:16 +00002179 clock_rates[0] = pjsua_var.media_cfg.snd_clock_rate;
2180 if (clock_rates[0] == 0)
2181 clock_rates[0] = pjsua_var.media_cfg.clock_rate;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002182
Benny Prijono50f19b32008-03-11 13:15:43 +00002183 /* Get the port0 of the conference bridge. */
2184 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2185 pj_assert(conf_port != NULL);
2186
Benny Prijono26056d82006-10-11 16:03:41 +00002187 /* Attempts to open the sound device with different clock rates */
2188 for (i=0; i<PJ_ARRAY_SIZE(clock_rates); ++i) {
2189 char errmsg[PJ_ERR_MSG_SIZE];
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002190 unsigned samples_per_frame;
Benny Prijono26056d82006-10-11 16:03:41 +00002191
2192 PJ_LOG(4,(THIS_FILE,
2193 "pjsua_set_snd_dev(): attempting to open devices "
2194 "@%d Hz", clock_rates[i]));
2195
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002196 samples_per_frame = clock_rates[i] *
2197 pjsua_var.media_cfg.audio_frame_ptime *
2198 pjsua_var.media_cfg.channel_count / 1000;
2199
Benny Prijono26056d82006-10-11 16:03:41 +00002200 /* Create the sound device. Sound port will start immediately. */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002201 status = pjmedia_snd_port_create(pjsua_var.snd_pool, capture_dev,
Benny Prijono26056d82006-10-11 16:03:41 +00002202 playback_dev,
Benny Prijono7d60d052008-03-29 12:24:20 +00002203 clock_rates[i],
2204 pjsua_var.media_cfg.channel_count,
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002205 samples_per_frame,
Benny Prijono26056d82006-10-11 16:03:41 +00002206 16, 0, &pjsua_var.snd_port);
2207
Benny Prijono658a1c52006-10-11 21:56:16 +00002208 if (status == PJ_SUCCESS) {
2209 selected_clock_rate = clock_rates[i];
Benny Prijono50f19b32008-03-11 13:15:43 +00002210
2211 /* If there's mismatch between sound port and conference's port,
2212 * create a resample port to bridge them.
2213 */
2214 if (selected_clock_rate != pjsua_var.media_cfg.clock_rate) {
2215 pjmedia_port *resample_port;
2216 unsigned resample_opt = 0;
2217
2218 if (pjsua_var.media_cfg.quality >= 3 &&
2219 pjsua_var.media_cfg.quality <= 4)
2220 {
Nanang Izzuddine85a1832008-05-29 11:03:23 +00002221 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
Benny Prijono50f19b32008-03-11 13:15:43 +00002222 }
2223 else if (pjsua_var.media_cfg.quality < 3) {
Nanang Izzuddine85a1832008-05-29 11:03:23 +00002224 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
Benny Prijono50f19b32008-03-11 13:15:43 +00002225 }
2226
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002227 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
Benny Prijono50f19b32008-03-11 13:15:43 +00002228 conf_port,
2229 selected_clock_rate,
2230 resample_opt,
2231 &resample_port);
2232 if (status != PJ_SUCCESS) {
2233 pj_strerror(status, errmsg, sizeof(errmsg));
2234 PJ_LOG(4, (THIS_FILE,
2235 "Error creating resample port, trying next "
2236 "clock rate",
2237 errmsg));
2238 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2239 pjsua_var.snd_port = NULL;
2240 continue;
2241 } else {
2242 conf_port = resample_port;
2243 break;
2244 }
2245
2246 } else {
2247 break;
2248 }
Benny Prijono658a1c52006-10-11 21:56:16 +00002249 }
Benny Prijono26056d82006-10-11 16:03:41 +00002250
2251 pj_strerror(status, errmsg, sizeof(errmsg));
Benny Prijono658a1c52006-10-11 21:56:16 +00002252 PJ_LOG(4, (THIS_FILE, "..failed: %s", errmsg));
Benny Prijono26056d82006-10-11 16:03:41 +00002253 }
2254
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002255 if (status != PJ_SUCCESS) {
2256 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
2257 return status;
2258 }
2259
Benny Prijonof20687a2006-08-04 18:27:19 +00002260 /* Set AEC */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002261 pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.snd_pool,
Benny Prijono5da50432006-08-07 10:24:52 +00002262 pjsua_var.media_cfg.ec_tail_len,
2263 pjsua_var.media_cfg.ec_options);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002264
Benny Prijono52a93912006-08-04 20:54:37 +00002265 /* Connect sound port to the bridge */
2266 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
2267 conf_port );
2268 if (status != PJ_SUCCESS) {
2269 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
2270 "sound device", status);
2271 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2272 pjsua_var.snd_port = NULL;
2273 return status;
2274 }
2275
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002276 /* Save the device IDs */
2277 pjsua_var.cap_dev = capture_dev;
2278 pjsua_var.play_dev = playback_dev;
2279
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002280 /* Update sound device name. */
2281 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2282 pjmedia_snd_stream_get_info(strm, &si);
2283 play_info = pjmedia_snd_get_dev_info(si.rec_id);
2284
Benny Prijonof3758ee2008-02-26 15:32:16 +00002285 if (si.clock_rate != pjsua_var.media_cfg.clock_rate) {
2286 char tmp_buf[128];
2287 int tmp_buf_len = sizeof(tmp_buf);
2288
2289 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1, "%s (%dKHz)",
2290 play_info->name, si.clock_rate/1000);
2291 pj_strset(&tmp, tmp_buf, tmp_buf_len);
2292 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
2293 } else {
2294 pjmedia_conf_set_port0_name(pjsua_var.mconf,
2295 pj_cstr(&tmp, play_info->name));
2296 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002297
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002298 return PJ_SUCCESS;
2299}
2300
2301
2302/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00002303 * Get currently active sound devices. If sound devices has not been created
2304 * (for example when pjsua_start() is not called), it is possible that
2305 * the function returns PJ_SUCCESS with -1 as device IDs.
2306 */
2307PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
2308 int *playback_dev)
2309{
2310 if (capture_dev) {
2311 *capture_dev = pjsua_var.cap_dev;
2312 }
2313 if (playback_dev) {
2314 *playback_dev = pjsua_var.play_dev;
2315 }
2316
2317 return PJ_SUCCESS;
2318}
2319
2320
2321/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002322 * Use null sound device.
2323 */
2324PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
2325{
2326 pjmedia_port *conf_port;
2327 pj_status_t status;
2328
2329 /* Close existing sound device */
2330 close_snd_dev();
2331
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002332 /* Create memory pool for sound device. */
2333 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2334 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2335
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002336 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
2337
2338 /* Get the port0 of the conference bridge. */
2339 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2340 pj_assert(conf_port != NULL);
2341
2342 /* Create master port, connecting port0 of the conference bridge to
2343 * a null port.
2344 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002345 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002346 conf_port, 0, &pjsua_var.null_snd);
2347 if (status != PJ_SUCCESS) {
2348 pjsua_perror(THIS_FILE, "Unable to create null sound device",
2349 status);
2350 return status;
2351 }
2352
2353 /* Start the master port */
2354 status = pjmedia_master_port_start(pjsua_var.null_snd);
2355 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2356
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002357 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2358 pjsua_var.play_dev = NULL_SND_DEV_ID;
2359
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002360 return PJ_SUCCESS;
2361}
2362
2363
Benny Prijonoe909eac2006-07-27 22:04:56 +00002364
2365/*
2366 * Use no device!
2367 */
2368PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2369{
2370 /* Close existing sound device */
2371 close_snd_dev();
2372
2373 pjsua_var.no_snd = PJ_TRUE;
2374 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2375}
2376
2377
Benny Prijonof20687a2006-08-04 18:27:19 +00002378/*
2379 * Configure the AEC settings of the sound port.
2380 */
Benny Prijono5da50432006-08-07 10:24:52 +00002381PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00002382{
2383 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2384
2385 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00002386 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
2387 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00002388
2389 return PJ_SUCCESS;
2390}
2391
2392
2393/*
2394 * Get current AEC tail length.
2395 */
Benny Prijono22dfe592006-08-06 12:07:13 +00002396PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00002397{
2398 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2399 return PJ_SUCCESS;
2400}
2401
Benny Prijonoe909eac2006-07-27 22:04:56 +00002402
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002403/*****************************************************************************
2404 * Codecs.
2405 */
2406
2407/*
2408 * Enum all supported codecs in the system.
2409 */
2410PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
2411 unsigned *p_count )
2412{
2413 pjmedia_codec_mgr *codec_mgr;
2414 pjmedia_codec_info info[32];
2415 unsigned i, count, prio[32];
2416 pj_status_t status;
2417
2418 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2419 count = PJ_ARRAY_SIZE(info);
2420 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
2421 if (status != PJ_SUCCESS) {
2422 *p_count = 0;
2423 return status;
2424 }
2425
2426 if (count > *p_count) count = *p_count;
2427
2428 for (i=0; i<count; ++i) {
2429 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
2430 id[i].codec_id = pj_str(id[i].buf_);
2431 id[i].priority = (pj_uint8_t) prio[i];
2432 }
2433
2434 *p_count = count;
2435
2436 return PJ_SUCCESS;
2437}
2438
2439
2440/*
2441 * Change codec priority.
2442 */
2443PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
2444 pj_uint8_t priority )
2445{
Benny Prijono88accae2008-06-26 15:48:14 +00002446 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002447 pjmedia_codec_mgr *codec_mgr;
2448
2449 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2450
Benny Prijono88accae2008-06-26 15:48:14 +00002451 if (codec_id->slen==1 && *codec_id->ptr=='*')
2452 codec_id = &all;
2453
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002454 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
2455 priority);
2456}
2457
2458
2459/*
2460 * Get codec parameters.
2461 */
2462PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
2463 pjmedia_codec_param *param )
2464{
Benny Prijono88accae2008-06-26 15:48:14 +00002465 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002466 const pjmedia_codec_info *info;
2467 pjmedia_codec_mgr *codec_mgr;
2468 unsigned count = 1;
2469 pj_status_t status;
2470
2471 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2472
Benny Prijono88accae2008-06-26 15:48:14 +00002473 if (codec_id->slen==1 && *codec_id->ptr=='*')
2474 codec_id = &all;
2475
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002476 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
2477 &count, &info, NULL);
2478 if (status != PJ_SUCCESS)
2479 return status;
2480
2481 if (count != 1)
2482 return PJ_ENOTFOUND;
2483
2484 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
2485 return status;
2486}
2487
2488
2489/*
2490 * Set codec parameters.
2491 */
2492PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
2493 const pjmedia_codec_param *param)
2494{
Benny Prijono00cae612006-07-31 15:19:36 +00002495 PJ_UNUSED_ARG(id);
2496 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002497 PJ_TODO(set_codec_param);
2498 return PJ_SUCCESS;
2499}