blob: 91d3ecb9794a8172e3b5a7e6d3d7e8615bfd983f [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 */
94#if PJMEDIA_HAS_SPEEX_CODEC
95 /* Register speex. */
Nanang Izzuddin9dbad152008-06-10 18:56:10 +000096 status = pjmedia_codec_speex_init(pjsua_var.med_endpt,
97 0,
98 pjsua_var.media_cfg.quality,
99 -1);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000100 if (status != PJ_SUCCESS) {
101 pjsua_perror(THIS_FILE, "Error initializing Speex codec",
102 status);
103 return status;
104 }
Benny Prijono7ca96da2006-08-07 12:11:40 +0000105
106 /* Set speex/16000 to higher priority*/
107 codec_id = pj_str("speex/16000");
108 pjmedia_codec_mgr_set_codec_priority(
109 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
110 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
111
112 /* Set speex/8000 to next higher priority*/
113 codec_id = pj_str("speex/8000");
114 pjmedia_codec_mgr_set_codec_priority(
115 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
116 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
117
118
119
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000120#endif /* PJMEDIA_HAS_SPEEX_CODEC */
121
Benny Prijono00cae612006-07-31 15:19:36 +0000122#if PJMEDIA_HAS_ILBC_CODEC
123 /* Register iLBC. */
124 status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt,
125 pjsua_var.media_cfg.ilbc_mode);
126 if (status != PJ_SUCCESS) {
127 pjsua_perror(THIS_FILE, "Error initializing iLBC codec",
128 status);
129 return status;
130 }
131#endif /* PJMEDIA_HAS_ILBC_CODEC */
132
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000133#if PJMEDIA_HAS_GSM_CODEC
134 /* Register GSM */
135 status = pjmedia_codec_gsm_init(pjsua_var.med_endpt);
136 if (status != PJ_SUCCESS) {
137 pjsua_perror(THIS_FILE, "Error initializing GSM codec",
138 status);
139 return status;
140 }
141#endif /* PJMEDIA_HAS_GSM_CODEC */
142
143#if PJMEDIA_HAS_G711_CODEC
144 /* Register PCMA and PCMU */
145 status = pjmedia_codec_g711_init(pjsua_var.med_endpt);
146 if (status != PJ_SUCCESS) {
147 pjsua_perror(THIS_FILE, "Error initializing G711 codec",
148 status);
149 return status;
150 }
151#endif /* PJMEDIA_HAS_G711_CODEC */
152
Benny Prijono7ffd7752008-03-17 14:07:53 +0000153#if PJMEDIA_HAS_G722_CODEC
154 status = pjmedia_codec_g722_init( pjsua_var.med_endpt );
155 if (status != PJ_SUCCESS) {
156 pjsua_perror(THIS_FILE, "Error initializing G722 codec",
157 status);
158 return status;
159 }
160#endif /* PJMEDIA_HAS_G722_CODEC */
161
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000162#if PJMEDIA_HAS_L16_CODEC
163 /* Register L16 family codecs, but disable all */
164 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
165 if (status != PJ_SUCCESS) {
166 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
167 status);
168 return status;
169 }
170
171 /* Disable ALL L16 codecs */
172 codec_id = pj_str("L16");
173 pjmedia_codec_mgr_set_codec_priority(
174 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
175 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
176
177#endif /* PJMEDIA_HAS_L16_CODEC */
178
179
180 /* Save additional conference bridge parameters for future
181 * reference.
182 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000183 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000184 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000185 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
186 pjsua_var.mconf_cfg.channel_count *
187 pjsua_var.media_cfg.audio_frame_ptime /
188 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000189
Benny Prijono0498d902006-06-19 14:49:14 +0000190 /* Init options for conference bridge. */
191 opt = PJMEDIA_CONF_NO_DEVICE;
192 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000193 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000194 {
195 opt |= PJMEDIA_CONF_SMALL_FILTER;
196 }
197 else if (pjsua_var.media_cfg.quality < 3) {
198 opt |= PJMEDIA_CONF_USE_LINEAR;
199 }
200
201
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000202 /* Init conference bridge. */
203 status = pjmedia_conf_create(pjsua_var.pool,
204 pjsua_var.media_cfg.max_media_ports,
205 pjsua_var.media_cfg.clock_rate,
206 pjsua_var.mconf_cfg.channel_count,
207 pjsua_var.mconf_cfg.samples_per_frame,
208 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000209 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000210 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000211 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000212 status);
213 return status;
214 }
215
216 /* Create null port just in case user wants to use null sound. */
217 status = pjmedia_null_port_create(pjsua_var.pool,
218 pjsua_var.media_cfg.clock_rate,
219 pjsua_var.mconf_cfg.channel_count,
220 pjsua_var.mconf_cfg.samples_per_frame,
221 pjsua_var.mconf_cfg.bits_per_sample,
222 &pjsua_var.null_port);
223 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
224
Benny Prijono6ba8c542007-10-16 01:34:14 +0000225 /* Perform NAT detection */
226 pjsua_detect_nat_type();
227
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000228 return PJ_SUCCESS;
229}
230
231
232/*
233 * Create RTP and RTCP socket pair, and possibly resolve their public
234 * address via STUN.
235 */
236static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
237 pjmedia_sock_info *skinfo)
238{
239 enum {
240 RTP_RETRY = 100
241 };
242 int i;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000243 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000244 pj_sockaddr_in mapped_addr[2];
245 pj_status_t status = PJ_SUCCESS;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000246 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000247 pj_sock_t sock[2];
248
Benny Prijonoc97608e2007-03-23 16:34:20 +0000249 /* Make sure STUN server resolution has completed */
250 status = pjsua_resolve_stun_server(PJ_TRUE);
251 if (status != PJ_SUCCESS) {
252 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
253 return status;
254 }
255
Benny Prijonode479562007-03-15 10:23:55 +0000256 if (next_rtp_port == 0)
257 next_rtp_port = (pj_uint16_t)cfg->port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000258
259 for (i=0; i<2; ++i)
260 sock[i] = PJ_INVALID_SOCKET;
261
Benny Prijono0a5cad82006-09-26 13:21:02 +0000262 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
263 if (cfg->bound_addr.slen) {
264 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
265 if (status != PJ_SUCCESS) {
266 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
267 status);
268 return status;
269 }
270 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000271
272 /* Loop retry to bind RTP and RTCP sockets. */
Benny Prijonode479562007-03-15 10:23:55 +0000273 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000274
275 /* Create and bind RTP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000276 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000277 if (status != PJ_SUCCESS) {
278 pjsua_perror(THIS_FILE, "socket() error", status);
279 return status;
280 }
281
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000282 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
283 next_rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000284 if (status != PJ_SUCCESS) {
285 pj_sock_close(sock[0]);
286 sock[0] = PJ_INVALID_SOCKET;
287 continue;
288 }
289
290 /* Create and bind RTCP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000291 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000292 if (status != PJ_SUCCESS) {
293 pjsua_perror(THIS_FILE, "socket() error", status);
294 pj_sock_close(sock[0]);
295 return status;
296 }
297
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000298 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
299 (pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000300 if (status != PJ_SUCCESS) {
301 pj_sock_close(sock[0]);
302 sock[0] = PJ_INVALID_SOCKET;
303
304 pj_sock_close(sock[1]);
305 sock[1] = PJ_INVALID_SOCKET;
306 continue;
307 }
308
309 /*
310 * If we're configured to use STUN, then find out the mapped address,
311 * and make sure that the mapped RTCP port is adjacent with the RTP.
312 */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000313 if (pjsua_var.stun_srv.addr.sa_family != 0) {
314 char ip_addr[32];
315 pj_str_t stun_srv;
316
317 pj_ansi_strcpy(ip_addr,
318 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
319 stun_srv = pj_str(ip_addr);
320
Benny Prijono14c2b862007-02-21 00:40:05 +0000321 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000322 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
323 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000324 mapped_addr);
325 if (status != PJ_SUCCESS) {
326 pjsua_perror(THIS_FILE, "STUN resolve error", status);
327 goto on_error;
328 }
329
Benny Prijono80eee892007-11-03 22:43:23 +0000330#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000331 if (pj_ntohs(mapped_addr[1].sin_port) ==
332 pj_ntohs(mapped_addr[0].sin_port)+1)
333 {
334 /* Success! */
335 break;
336 }
337
338 pj_sock_close(sock[0]);
339 sock[0] = PJ_INVALID_SOCKET;
340
341 pj_sock_close(sock[1]);
342 sock[1] = PJ_INVALID_SOCKET;
Benny Prijono80eee892007-11-03 22:43:23 +0000343#else
344 if (pj_ntohs(mapped_addr[1].sin_port) !=
345 pj_ntohs(mapped_addr[0].sin_port)+1)
346 {
347 PJ_LOG(4,(THIS_FILE,
348 "Note: STUN mapped RTCP port %d is not adjacent"
349 " to RTP port %d",
350 pj_ntohs(mapped_addr[1].sin_port),
351 pj_ntohs(mapped_addr[0].sin_port)));
352 }
353 /* Success! */
354 break;
355#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000356
Benny Prijono0a5cad82006-09-26 13:21:02 +0000357 } else if (cfg->public_addr.slen) {
358
359 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000360 (pj_uint16_t)next_rtp_port);
Benny Prijono0a5cad82006-09-26 13:21:02 +0000361 if (status != PJ_SUCCESS)
362 goto on_error;
363
364 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000365 (pj_uint16_t)(next_rtp_port+1));
Benny Prijono0a5cad82006-09-26 13:21:02 +0000366 if (status != PJ_SUCCESS)
367 goto on_error;
368
369 break;
370
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000371 } else {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000372
Benny Prijono42d08d22007-12-20 11:23:07 +0000373 if (bound_addr.sin_addr.s_addr == 0) {
374 pj_sockaddr addr;
375
376 /* Get local IP address. */
377 status = pj_gethostip(pj_AF_INET(), &addr);
378 if (status != PJ_SUCCESS)
379 goto on_error;
380
381 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
382 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000383
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000384 for (i=0; i<2; ++i) {
385 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
Benny Prijono42d08d22007-12-20 11:23:07 +0000386 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000387 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000388
Benny Prijonode479562007-03-15 10:23:55 +0000389 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
390 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000391 break;
392 }
393 }
394
395 if (sock[0] == PJ_INVALID_SOCKET) {
396 PJ_LOG(1,(THIS_FILE,
397 "Unable to find appropriate RTP/RTCP ports combination"));
398 goto on_error;
399 }
400
401
402 skinfo->rtp_sock = sock[0];
403 pj_memcpy(&skinfo->rtp_addr_name,
404 &mapped_addr[0], sizeof(pj_sockaddr_in));
405
406 skinfo->rtcp_sock = sock[1];
407 pj_memcpy(&skinfo->rtcp_addr_name,
408 &mapped_addr[1], sizeof(pj_sockaddr_in));
409
Benny Prijono8b22ce12008-02-08 12:57:55 +0000410 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000411 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
412 sizeof(addr_buf), 3)));
Benny Prijono8b22ce12008-02-08 12:57:55 +0000413 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000414 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
415 sizeof(addr_buf), 3)));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000416
Benny Prijonode479562007-03-15 10:23:55 +0000417 next_rtp_port += 2;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000418 return PJ_SUCCESS;
419
420on_error:
421 for (i=0; i<2; ++i) {
422 if (sock[i] != PJ_INVALID_SOCKET)
423 pj_sock_close(sock[i]);
424 }
425 return status;
426}
427
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000428/* Check if sound device is idle. */
429static void check_snd_dev_idle()
430{
431
432 /* Activate sound device auto-close timer if sound device is idle.
433 * It is idle when there is no port connection in the bridge.
434 */
435 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) &&
436 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
437 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
438 pjsua_var.media_cfg.snd_auto_close_time >= 0)
439 {
440 pj_time_val delay;
441
442 delay.msec = 0;
443 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
444
445 pjsua_var.snd_idle_timer.id = PJ_TRUE;
446 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
447 &delay);
448 }
449}
450
451
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000452/* Timer callback to close sound device */
453static void close_snd_timer_cb( pj_timer_heap_t *th,
454 pj_timer_entry *entry)
455{
456 PJ_UNUSED_ARG(th);
457
458 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
459 pjsua_var.media_cfg.snd_auto_close_time));
460
461 entry->id = PJ_FALSE;
462
463 close_snd_dev();
464}
465
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000466
467/*
468 * Start pjsua media subsystem.
469 */
470pj_status_t pjsua_media_subsys_start(void)
471{
472 pj_status_t status;
473
474 /* Create media for calls, if none is specified */
475 if (pjsua_var.calls[0].med_tp == NULL) {
476 pjsua_transport_config transport_cfg;
477
478 /* Create default transport config */
479 pjsua_transport_config_default(&transport_cfg);
480 transport_cfg.port = DEFAULT_RTP_PORT;
481
482 status = pjsua_media_transports_create(&transport_cfg);
483 if (status != PJ_SUCCESS)
484 return status;
485 }
486
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000487 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
488 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000489
490 return PJ_SUCCESS;
491}
492
493
494/*
495 * Destroy pjsua media subsystem.
496 */
497pj_status_t pjsua_media_subsys_destroy(void)
498{
499 unsigned i;
500
501 close_snd_dev();
502
503 if (pjsua_var.mconf) {
504 pjmedia_conf_destroy(pjsua_var.mconf);
505 pjsua_var.mconf = NULL;
506 }
507
508 if (pjsua_var.null_port) {
509 pjmedia_port_destroy(pjsua_var.null_port);
510 pjsua_var.null_port = NULL;
511 }
512
513 /* Destroy file players */
514 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
515 if (pjsua_var.player[i].port) {
516 pjmedia_port_destroy(pjsua_var.player[i].port);
517 pjsua_var.player[i].port = NULL;
518 }
519 }
520
521 /* Destroy file recorders */
522 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
523 if (pjsua_var.recorder[i].port) {
524 pjmedia_port_destroy(pjsua_var.recorder[i].port);
525 pjsua_var.recorder[i].port = NULL;
526 }
527 }
528
529 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000530 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000531 if (pjsua_var.calls[i].med_tp) {
532 (*pjsua_var.calls[i].med_tp->op->destroy)(pjsua_var.calls[i].med_tp);
533 pjsua_var.calls[i].med_tp = NULL;
534 }
535 }
536
537 /* Destroy media endpoint. */
538 if (pjsua_var.med_endpt) {
539
540 /* Shutdown all codecs: */
541# if PJMEDIA_HAS_SPEEX_CODEC
542 pjmedia_codec_speex_deinit();
543# endif /* PJMEDIA_HAS_SPEEX_CODEC */
544
545# if PJMEDIA_HAS_GSM_CODEC
546 pjmedia_codec_gsm_deinit();
547# endif /* PJMEDIA_HAS_GSM_CODEC */
548
549# if PJMEDIA_HAS_G711_CODEC
550 pjmedia_codec_g711_deinit();
551# endif /* PJMEDIA_HAS_G711_CODEC */
552
Benny Prijono7ffd7752008-03-17 14:07:53 +0000553# if PJMEDIA_HAS_G722_CODEC
554 pjmedia_codec_g722_deinit();
555# endif /* PJMEDIA_HAS_G722_CODEC */
556
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000557# if PJMEDIA_HAS_L16_CODEC
558 pjmedia_codec_l16_deinit();
559# endif /* PJMEDIA_HAS_L16_CODEC */
560
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000561 pjmedia_endpt_destroy(pjsua_var.med_endpt);
562 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000563
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000564 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000565 // Not necessary, as pjmedia_snd_deinit() should have been called
566 // in pjmedia_endpt_destroy().
567 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000568 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000569
Benny Prijonode479562007-03-15 10:23:55 +0000570 /* Reset RTP port */
571 next_rtp_port = 0;
572
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000573 return PJ_SUCCESS;
574}
575
576
Benny Prijonoc97608e2007-03-23 16:34:20 +0000577/* Create normal UDP media transports */
578static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000579{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000580 unsigned i;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000581 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000582 pj_status_t status;
583
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000584 /* Create each media transport */
585 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
586
Benny Prijono617c5bc2007-04-02 19:51:21 +0000587 status = create_rtp_rtcp_sock(cfg, &skinfo);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000588 if (status != PJ_SUCCESS) {
589 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
590 status);
591 goto on_error;
592 }
Benny Prijonod8179652008-01-23 20:39:07 +0000593
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000594 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000595 &skinfo, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000596 &pjsua_var.calls[i].med_tp);
597 if (status != PJ_SUCCESS) {
598 pjsua_perror(THIS_FILE, "Unable to create media transport",
599 status);
600 goto on_error;
601 }
Benny Prijono00cae612006-07-31 15:19:36 +0000602
Benny Prijonod8179652008-01-23 20:39:07 +0000603 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
604 PJMEDIA_DIR_ENCODING,
605 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000606
Benny Prijonod8179652008-01-23 20:39:07 +0000607 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
608 PJMEDIA_DIR_DECODING,
609 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000610
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000611 }
612
Benny Prijonoc97608e2007-03-23 16:34:20 +0000613 return PJ_SUCCESS;
614
615on_error:
616 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
617 if (pjsua_var.calls[i].med_tp != NULL) {
618 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
619 pjsua_var.calls[i].med_tp = NULL;
620 }
621 }
622
623 return status;
624}
625
626
Benny Prijono096c56c2007-09-15 08:30:16 +0000627/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000628static void on_ice_complete(pjmedia_transport *tp,
629 pj_ice_strans_op op,
630 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000631{
Benny Prijonof76e1392008-06-06 14:51:48 +0000632 unsigned id;
Benny Prijono096c56c2007-09-15 08:30:16 +0000633 pj_bool_t found = PJ_FALSE;
634
Benny Prijono096c56c2007-09-15 08:30:16 +0000635 /* Find call which has this media transport */
636
637 PJSUA_LOCK();
638
Benny Prijonof76e1392008-06-06 14:51:48 +0000639 for (id=0; id<pjsua_var.ua_cfg.max_calls; ++id) {
640 if (pjsua_var.calls[id].med_tp == tp ||
641 pjsua_var.calls[id].med_orig == tp)
642 {
643 found = PJ_TRUE;
644 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000645 }
646 }
647
648 PJSUA_UNLOCK();
649
Benny Prijonof76e1392008-06-06 14:51:48 +0000650 if (!found)
651 return;
652
653 switch (op) {
654 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono224b4e22008-06-19 14:10:28 +0000655 pjsua_var.calls[id].med_tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000656 break;
657 case PJ_ICE_STRANS_OP_NEGOTIATION:
658 if (result != PJ_SUCCESS) {
659 pjsua_var.calls[id].media_st = PJSUA_CALL_MEDIA_ERROR;
660 pjsua_var.calls[id].media_dir = PJMEDIA_DIR_NONE;
661
662 if (pjsua_var.ua_cfg.cb.on_call_media_state) {
663 pjsua_var.ua_cfg.cb.on_call_media_state(id);
664 }
665 }
666 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000667 }
668}
669
670
Benny Prijonof76e1392008-06-06 14:51:48 +0000671/* Parse "HOST:PORT" format */
672static pj_status_t parse_host_port(const pj_str_t *host_port,
673 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000674{
Benny Prijonof76e1392008-06-06 14:51:48 +0000675 pj_str_t str_port;
676
677 str_port.ptr = pj_strchr(host_port, ':');
678 if (str_port.ptr != NULL) {
679 int iport;
680
681 host->ptr = host_port->ptr;
682 host->slen = (str_port.ptr - host->ptr);
683 str_port.ptr++;
684 str_port.slen = host_port->slen - host->slen - 1;
685 iport = (int)pj_strtoul(&str_port);
686 if (iport < 1 || iport > 65535)
687 return PJ_EINVAL;
688 *port = (pj_uint16_t)iport;
689 } else {
690 *host = *host_port;
691 *port = 0;
692 }
693
694 return PJ_SUCCESS;
695}
696
697/* Create ICE media transports (when ice is enabled) */
698static pj_status_t create_ice_media_transports(void)
699{
700 char stunip[PJ_INET6_ADDRSTRLEN];
701 pj_ice_strans_cfg ice_cfg;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000702 unsigned i;
703 pj_status_t status;
704
Benny Prijonoda9785b2007-04-02 20:43:06 +0000705 /* Make sure STUN server resolution has completed */
706 status = pjsua_resolve_stun_server(PJ_TRUE);
707 if (status != PJ_SUCCESS) {
708 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
709 return status;
710 }
711
Benny Prijonof76e1392008-06-06 14:51:48 +0000712 /* Create ICE stream transport configuration */
713 pj_ice_strans_cfg_default(&ice_cfg);
714 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
715 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
716 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
717
718 ice_cfg.af = pj_AF_INET();
719 ice_cfg.resolver = pjsua_var.resolver;
720
721 /* Configure STUN settings */
722 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
723 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
724 ice_cfg.stun.server = pj_str(stunip);
725 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
726 }
727 ice_cfg.stun.no_host_cands = pjsua_var.media_cfg.ice_no_host_cands;
728
729 /* Configure TURN settings */
730 if (pjsua_var.media_cfg.enable_turn) {
731 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
732 &ice_cfg.turn.server,
733 &ice_cfg.turn.port);
734 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
735 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
736 return PJ_EINVAL;
737 }
738 if (ice_cfg.turn.port == 0)
739 ice_cfg.turn.port = 3479;
740 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
741 pj_memcpy(&ice_cfg.turn.auth_cred,
742 &pjsua_var.media_cfg.turn_auth_cred,
743 sizeof(ice_cfg.turn.auth_cred));
744 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000745
Benny Prijonoc97608e2007-03-23 16:34:20 +0000746 /* Create each media transport */
747 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono096c56c2007-09-15 08:30:16 +0000748 pjmedia_ice_cb ice_cb;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000749 char name[32];
Benny Prijonof76e1392008-06-06 14:51:48 +0000750 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000751
Benny Prijono096c56c2007-09-15 08:30:16 +0000752 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
753 ice_cb.on_ice_complete = &on_ice_complete;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000754 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i);
Benny Prijono224b4e22008-06-19 14:10:28 +0000755 pjsua_var.calls[i].med_tp_ready = PJ_EPENDING;
Benny Prijonof76e1392008-06-06 14:51:48 +0000756
757 comp_cnt = 1;
758 if (PJMEDIA_ADVERTISE_RTCP)
759 ++comp_cnt;
760
761 status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
762 &ice_cfg, &ice_cb,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000763 &pjsua_var.calls[i].med_tp);
764 if (status != PJ_SUCCESS) {
765 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
766 status);
767 goto on_error;
768 }
769
Benny Prijonof76e1392008-06-06 14:51:48 +0000770 /* Wait until transport is initialized, or time out */
771 PJSUA_UNLOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000772 while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000773 pjsua_handle_events(100);
774 }
775 PJSUA_LOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000776 if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000777 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
Benny Prijono224b4e22008-06-19 14:10:28 +0000778 pjsua_var.calls[i].med_tp_ready);
779 status = pjsua_var.calls[i].med_tp_ready;
Benny Prijonof76e1392008-06-06 14:51:48 +0000780 goto on_error;
781 }
782
Benny Prijonod8179652008-01-23 20:39:07 +0000783 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
784 PJMEDIA_DIR_ENCODING,
785 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono11da9bc2007-09-15 08:55:00 +0000786
Benny Prijonod8179652008-01-23 20:39:07 +0000787 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
788 PJMEDIA_DIR_DECODING,
789 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000790 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000791
792 return PJ_SUCCESS;
793
794on_error:
795 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
796 if (pjsua_var.calls[i].med_tp != NULL) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000797 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000798 pjsua_var.calls[i].med_tp = NULL;
799 }
800 }
801
Benny Prijonoc97608e2007-03-23 16:34:20 +0000802 return status;
803}
804
805
806/*
807 * Create UDP media transports for all the calls. This function creates
808 * one UDP media transport for each call.
809 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +0000810PJ_DEF(pj_status_t) pjsua_media_transports_create(
811 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000812{
813 pjsua_transport_config cfg;
814 unsigned i;
815 pj_status_t status;
816
817
818 /* Make sure pjsua_init() has been called */
819 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
820
821 PJSUA_LOCK();
822
823 /* Delete existing media transports */
824 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
825 if (pjsua_var.calls[i].med_tp != NULL) {
826 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
827 pjsua_var.calls[i].med_tp = NULL;
828 }
829 }
830
831 /* Copy config */
832 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
833
834 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000835 status = create_ice_media_transports();
Benny Prijonoc97608e2007-03-23 16:34:20 +0000836 } else {
837 status = create_udp_media_transports(&cfg);
838 }
839
840
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000841 PJSUA_UNLOCK();
842
843 return status;
844}
845
846
Benny Prijonoc97608e2007-03-23 16:34:20 +0000847pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
Benny Prijonod8179652008-01-23 20:39:07 +0000848 pjsip_role_e role,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000849 int security_level,
Benny Prijono224b4e22008-06-19 14:10:28 +0000850 pj_pool_t *tmp_pool,
851 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000852 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000853{
Benny Prijono224b4e22008-06-19 14:10:28 +0000854 enum { MEDIA_IDX = 0 };
Benny Prijonoc97608e2007-03-23 16:34:20 +0000855 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono224b4e22008-06-19 14:10:28 +0000856 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000857
Benny Prijonod8179652008-01-23 20:39:07 +0000858#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
859 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
860 pjmedia_srtp_setting srtp_opt;
861 pjmedia_transport *srtp;
Benny Prijonod8179652008-01-23 20:39:07 +0000862#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000863
Benny Prijonod8179652008-01-23 20:39:07 +0000864 PJ_UNUSED_ARG(role);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000865
Benny Prijonod8179652008-01-23 20:39:07 +0000866 /* Return error if media transport has not been created yet
867 * (e.g. application is starting)
868 */
869 if (call->med_tp == NULL) {
870 return PJ_EBUSY;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000871 }
872
Benny Prijonod8179652008-01-23 20:39:07 +0000873#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Benny Prijono53a7c702008-04-14 02:57:29 +0000874 /* This function may be called when SRTP transport already exists
875 * (e.g: in re-invite, update), don't need to destroy/re-create.
876 */
877 if (!call->med_orig || call->med_tp == call->med_orig) {
878
879 /* Check if SRTP requires secure signaling */
880 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
881 if (security_level < acc->cfg.srtp_secure_signaling) {
882 if (sip_err_code)
883 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
884 return PJSIP_ESESSIONINSECURE;
885 }
Benny Prijonod8179652008-01-23 20:39:07 +0000886 }
Benny Prijonod8179652008-01-23 20:39:07 +0000887
Benny Prijono53a7c702008-04-14 02:57:29 +0000888 /* Always create SRTP adapter */
889 pjmedia_srtp_setting_default(&srtp_opt);
890 srtp_opt.close_member_tp = PJ_FALSE;
891 srtp_opt.use = acc->cfg.use_srtp;
892 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
893 call->med_tp,
894 &srtp_opt, &srtp);
895 if (status != PJ_SUCCESS) {
896 if (sip_err_code)
897 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
898 return status;
899 }
Benny Prijonod8179652008-01-23 20:39:07 +0000900
Benny Prijono53a7c702008-04-14 02:57:29 +0000901 /* Set SRTP as current media transport */
902 call->med_orig = call->med_tp;
903 call->med_tp = srtp;
904 }
Benny Prijonod8179652008-01-23 20:39:07 +0000905#else
906 call->med_orig = call->med_tp;
907 PJ_UNUSED_ARG(security_level);
908#endif
909
Benny Prijono224b4e22008-06-19 14:10:28 +0000910 /* Create the media transport */
911 status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0,
912 rem_sdp, MEDIA_IDX);
913 if (status != PJ_SUCCESS) {
914 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
915 pjsua_media_channel_deinit(call_id);
916 return status;
917 }
918
919 call->med_tp_st = PJSUA_MED_TP_INIT;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000920 return PJ_SUCCESS;
921}
922
923pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
924 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +0000925 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000926 pjmedia_sdp_session **p_sdp,
927 int *sip_status_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000928{
Benny Prijonod8179652008-01-23 20:39:07 +0000929 enum { MAX_MEDIA = 1, MEDIA_IDX = 0 };
Benny Prijonoc97608e2007-03-23 16:34:20 +0000930 pjmedia_sdp_session *sdp;
Benny Prijonoe1a5a852008-03-11 21:38:05 +0000931 pjmedia_transport_info tpinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000932 pjsua_call *call = &pjsua_var.calls[call_id];
933 pj_status_t status;
934
Benny Prijono55e82352007-05-10 20:49:08 +0000935 /* Return error if media transport has not been created yet
936 * (e.g. application is starting)
937 */
938 if (call->med_tp == NULL) {
939 return PJ_EBUSY;
940 }
941
Benny Prijono224b4e22008-06-19 14:10:28 +0000942 /* Create media if it's not created. This could happen when call is
943 * currently on-hold
944 */
945 if (call->med_tp_st == PJSUA_MED_TP_IDLE) {
946 pjsip_role_e role;
947 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
948 status = pjsua_media_channel_init(call_id, role, call->secure_level,
949 pool, rem_sdp, sip_status_code);
950 if (status != PJ_SUCCESS)
951 return status;
952 }
953
Benny Prijono617c5bc2007-04-02 19:51:21 +0000954 /* Get media socket info */
Benny Prijono734fc2d2008-03-17 16:05:35 +0000955 pjmedia_transport_info_init(&tpinfo);
Benny Prijonoe1a5a852008-03-11 21:38:05 +0000956 pjmedia_transport_get_info(call->med_tp, &tpinfo);
Benny Prijono617c5bc2007-04-02 19:51:21 +0000957
958 /* Create SDP */
Benny Prijonod8179652008-01-23 20:39:07 +0000959 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA,
Benny Prijonoe1a5a852008-03-11 21:38:05 +0000960 &tpinfo.sock_info, &sdp);
Benny Prijono25b2ea12008-01-24 19:20:54 +0000961 if (status != PJ_SUCCESS) {
962 if (sip_status_code) *sip_status_code = 500;
Benny Prijono224b4e22008-06-19 14:10:28 +0000963 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +0000964 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000965
Benny Prijono6ba8c542007-10-16 01:34:14 +0000966 /* Add NAT info in the SDP */
967 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
968 pjmedia_sdp_attr *a;
969 pj_str_t value;
970 char nat_info[80];
971
972 value.ptr = nat_info;
973 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
974 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
975 "%d", pjsua_var.nat_type);
976 } else {
977 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
978 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
979 "%d %s",
980 pjsua_var.nat_type,
981 type_name);
982 }
983
984 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
985
986 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
987
988 }
989
Benny Prijonod8179652008-01-23 20:39:07 +0000990 /* Give the SDP to media transport */
Benny Prijono224b4e22008-06-19 14:10:28 +0000991 status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp,
992 MEDIA_IDX);
Benny Prijono25b2ea12008-01-24 19:20:54 +0000993 if (status != PJ_SUCCESS) {
994 if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono224b4e22008-06-19 14:10:28 +0000995 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +0000996 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000997
998 *p_sdp = sdp;
999 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001000}
1001
1002
1003static void stop_media_session(pjsua_call_id call_id)
1004{
1005 pjsua_call *call = &pjsua_var.calls[call_id];
1006
1007 if (call->conf_slot != PJSUA_INVALID_ID) {
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001008 if (pjsua_var.mconf) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001009 pjsua_conf_remove_port(call->conf_slot);
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001010 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001011 call->conf_slot = PJSUA_INVALID_ID;
1012 }
1013
1014 if (call->session) {
Benny Prijonofc13bf62008-02-20 08:56:15 +00001015 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1016 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0);
1017 }
1018
Benny Prijonoc97608e2007-03-23 16:34:20 +00001019 pjmedia_session_destroy(call->session);
1020 call->session = NULL;
1021
1022 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
1023 call_id));
1024
1025 }
1026
1027 call->media_st = PJSUA_CALL_MEDIA_NONE;
1028}
1029
1030pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1031{
1032 pjsua_call *call = &pjsua_var.calls[call_id];
1033
1034 stop_media_session(call_id);
1035
Benny Prijono224b4e22008-06-19 14:10:28 +00001036 if (call->med_tp_st != PJSUA_MED_TP_IDLE) {
1037 pjmedia_transport_media_stop(call->med_tp);
1038 call->med_tp_st = PJSUA_MED_TP_IDLE;
1039 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001040
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001041 if (call->med_orig && call->med_tp != call->med_orig) {
Benny Prijonod8179652008-01-23 20:39:07 +00001042 pjmedia_transport_close(call->med_tp);
1043 call->med_tp = call->med_orig;
1044 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001045 return PJ_SUCCESS;
1046}
1047
1048
1049/*
1050 * DTMF callback from the stream.
1051 */
1052static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1053 int digit)
1054{
1055 PJ_UNUSED_ARG(strm);
1056
Benny Prijono0c068262008-02-14 14:38:52 +00001057 /* For discussions about call mutex protection related to this
1058 * callback, please see ticket #460:
1059 * http://trac.pjsip.org/repos/ticket/460#comment:4
1060 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001061 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1062 pjsua_call_id call_id;
1063
Benny Prijonod8179652008-01-23 20:39:07 +00001064 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001065 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1066 }
1067}
1068
1069
1070pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijono224b4e22008-06-19 14:10:28 +00001071 const pjmedia_sdp_session *local_sdp,
Benny Prijonodbce2cf2007-03-28 16:24:00 +00001072 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001073{
Benny Prijono91e567e2007-12-28 08:51:58 +00001074 unsigned i;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001075 int prev_media_st = 0;
1076 pjsua_call *call = &pjsua_var.calls[call_id];
1077 pjmedia_session_info sess_info;
Benny Prijono91e567e2007-12-28 08:51:58 +00001078 pjmedia_stream_info *si = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001079 pjmedia_port *media_port;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001080 pj_status_t status;
1081
1082 /* Destroy existing media session, if any. */
1083 prev_media_st = call->media_st;
1084 stop_media_session(call->index);
1085
1086 /* Create media session info based on SDP parameters.
Benny Prijonoc97608e2007-03-23 16:34:20 +00001087 */
1088 status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
1089 pjsua_var.med_endpt,
Benny Prijono91e567e2007-12-28 08:51:58 +00001090 PJMEDIA_MAX_SDP_MEDIA, &sess_info,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001091 local_sdp, remote_sdp);
1092 if (status != PJ_SUCCESS)
1093 return status;
1094
Benny Prijono91e567e2007-12-28 08:51:58 +00001095 /* Find which session is audio (we only support audio for now) */
1096 for (i=0; i < sess_info.stream_cnt; ++i) {
1097 if (sess_info.stream_info[i].type == PJMEDIA_TYPE_AUDIO &&
Benny Prijonod8179652008-01-23 20:39:07 +00001098 (sess_info.stream_info[i].proto == PJMEDIA_TP_PROTO_RTP_AVP ||
1099 sess_info.stream_info[i].proto == PJMEDIA_TP_PROTO_RTP_SAVP))
Benny Prijono91e567e2007-12-28 08:51:58 +00001100 {
1101 si = &sess_info.stream_info[i];
1102 break;
1103 }
1104 }
1105
1106 if (si == NULL) {
1107 /* Not found */
1108 return PJMEDIA_EINVALIMEDIATYPE;
1109 }
1110
1111
1112 /* Reset session info with only one media stream */
1113 sess_info.stream_cnt = 1;
1114 if (si != &sess_info.stream_info[0])
1115 pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001116
1117 /* Check if media is put on-hold */
Benny Prijono91e567e2007-12-28 08:51:58 +00001118 if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001119 {
1120
1121 /* Determine who puts the call on-hold */
1122 if (prev_media_st == PJSUA_CALL_MEDIA_ACTIVE) {
1123 if (pjmedia_sdp_neg_was_answer_remote(call->inv->neg)) {
1124 /* It was local who offer hold */
1125 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
1126 } else {
1127 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
1128 }
1129 }
1130
1131 call->media_dir = PJMEDIA_DIR_NONE;
1132
Benny Prijonod8179652008-01-23 20:39:07 +00001133 /* Shutdown transport's session */
1134 pjmedia_transport_media_stop(call->med_tp);
Benny Prijono224b4e22008-06-19 14:10:28 +00001135 call->med_tp_st = PJSUA_MED_TP_IDLE;
Benny Prijono667952e2007-04-02 19:27:54 +00001136
Benny Prijonoc97608e2007-03-23 16:34:20 +00001137 /* No need because we need keepalive? */
1138
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001139 /* Close upper entry of transport stack */
1140 if (call->med_orig && (call->med_tp != call->med_orig)) {
1141 pjmedia_transport_close(call->med_tp);
1142 call->med_tp = call->med_orig;
1143 }
1144
Benny Prijonoc97608e2007-03-23 16:34:20 +00001145 } else {
Benny Prijono224b4e22008-06-19 14:10:28 +00001146 /* Start/restart media transport */
Benny Prijonod8179652008-01-23 20:39:07 +00001147 status = pjmedia_transport_media_start(call->med_tp,
1148 call->inv->pool,
1149 local_sdp, remote_sdp, 0);
1150 if (status != PJ_SUCCESS)
1151 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001152
Benny Prijono224b4e22008-06-19 14:10:28 +00001153 call->med_tp_st = PJSUA_MED_TP_RUNNING;
1154
Benny Prijonoc97608e2007-03-23 16:34:20 +00001155 /* Override ptime, if this option is specified. */
1156 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001157 si->param->setting.frm_per_pkt = (pj_uint8_t)
1158 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1159 if (si->param->setting.frm_per_pkt == 0)
1160 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001161 }
1162
1163 /* Disable VAD, if this option is specified. */
1164 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001165 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001166 }
1167
1168
1169 /* Optionally, application may modify other stream settings here
1170 * (such as jitter buffer parameters, codec ptime, etc.)
1171 */
Benny Prijono91e567e2007-12-28 08:51:58 +00001172 si->jb_init = pjsua_var.media_cfg.jb_init;
1173 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1174 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1175 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001176
Benny Prijono8147f402007-11-21 14:50:07 +00001177 /* Set SSRC */
Benny Prijono91e567e2007-12-28 08:51:58 +00001178 si->ssrc = call->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00001179
Benny Prijonoc97608e2007-03-23 16:34:20 +00001180 /* Create session based on session info. */
1181 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
1182 &call->med_tp,
1183 call, &call->session );
1184 if (status != PJ_SUCCESS) {
1185 return status;
1186 }
1187
1188 /* If DTMF callback is installed by application, install our
1189 * callback to the session.
1190 */
1191 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1192 pjmedia_session_set_dtmf_callback(call->session, 0,
1193 &dtmf_callback,
Benny Prijonod8179652008-01-23 20:39:07 +00001194 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001195 }
1196
1197 /* Get the port interface of the first stream in the session.
1198 * We need the port interface to add to the conference bridge.
1199 */
1200 pjmedia_session_get_port(call->session, 0, &media_port);
1201
Benny Prijonofc13bf62008-02-20 08:56:15 +00001202 /* Notify application about stream creation.
1203 * Note: application may modify media_port to point to different
1204 * media port
1205 */
1206 if (pjsua_var.ua_cfg.cb.on_stream_created) {
1207 pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session,
1208 0, &media_port);
1209 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001210
1211 /*
1212 * Add the call to conference bridge.
1213 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001214 {
1215 char tmp[PJSIP_MAX_URL_SIZE];
1216 pj_str_t port_name;
1217
1218 port_name.ptr = tmp;
1219 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1220 call->inv->dlg->remote.info->uri,
1221 tmp, sizeof(tmp));
1222 if (port_name.slen < 1) {
1223 port_name = pj_str("call");
1224 }
1225 status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
1226 media_port,
1227 &port_name,
1228 (unsigned*)&call->conf_slot);
1229 if (status != PJ_SUCCESS) {
1230 return status;
1231 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001232 }
1233
1234 /* Call's media state is active */
1235 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijono91e567e2007-12-28 08:51:58 +00001236 call->media_dir = si->dir;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001237 }
1238
1239 /* Print info. */
1240 {
1241 char info[80];
1242 int info_len = 0;
1243 unsigned i;
1244
1245 for (i=0; i<sess_info.stream_cnt; ++i) {
1246 int len;
1247 const char *dir;
1248 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1249
1250 switch (strm_info->dir) {
1251 case PJMEDIA_DIR_NONE:
1252 dir = "inactive";
1253 break;
1254 case PJMEDIA_DIR_ENCODING:
1255 dir = "sendonly";
1256 break;
1257 case PJMEDIA_DIR_DECODING:
1258 dir = "recvonly";
1259 break;
1260 case PJMEDIA_DIR_ENCODING_DECODING:
1261 dir = "sendrecv";
1262 break;
1263 default:
1264 dir = "unknown";
1265 break;
1266 }
1267 len = pj_ansi_sprintf( info+info_len,
1268 ", stream #%d: %.*s (%s)", i,
1269 (int)strm_info->fmt.encoding_name.slen,
1270 strm_info->fmt.encoding_name.ptr,
1271 dir);
1272 if (len > 0)
1273 info_len += len;
1274 }
1275 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1276 }
1277
1278 return PJ_SUCCESS;
1279}
1280
1281
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001282/*
1283 * Get maxinum number of conference ports.
1284 */
1285PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1286{
1287 return pjsua_var.media_cfg.max_media_ports;
1288}
1289
1290
1291/*
1292 * Get current number of active ports in the bridge.
1293 */
1294PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1295{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001296 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001297 unsigned count = PJ_ARRAY_SIZE(ports);
1298 pj_status_t status;
1299
1300 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1301 if (status != PJ_SUCCESS)
1302 count = 0;
1303
1304 return count;
1305}
1306
1307
1308/*
1309 * Enumerate all conference ports.
1310 */
1311PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1312 unsigned *count)
1313{
1314 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1315}
1316
1317
1318/*
1319 * Get information about the specified conference port
1320 */
1321PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1322 pjsua_conf_port_info *info)
1323{
1324 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001325 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001326 pj_status_t status;
1327
1328 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1329 if (status != PJ_SUCCESS)
1330 return status;
1331
Benny Prijonoac623b32006-07-03 15:19:31 +00001332 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001333 info->slot_id = id;
1334 info->name = cinfo.name;
1335 info->clock_rate = cinfo.clock_rate;
1336 info->channel_count = cinfo.channel_count;
1337 info->samples_per_frame = cinfo.samples_per_frame;
1338 info->bits_per_sample = cinfo.bits_per_sample;
1339
1340 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001341 info->listener_cnt = cinfo.listener_cnt;
1342 for (i=0; i<cinfo.listener_cnt; ++i) {
1343 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001344 }
1345
1346 return PJ_SUCCESS;
1347}
1348
1349
1350/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001351 * Add arbitrary media port to PJSUA's conference bridge.
1352 */
1353PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1354 pjmedia_port *port,
1355 pjsua_conf_port_id *p_id)
1356{
1357 pj_status_t status;
1358
1359 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1360 port, NULL, (unsigned*)p_id);
1361 if (status != PJ_SUCCESS) {
1362 if (p_id)
1363 *p_id = PJSUA_INVALID_ID;
1364 }
1365
1366 return status;
1367}
1368
1369
1370/*
1371 * Remove arbitrary slot from the conference bridge.
1372 */
1373PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1374{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001375 pj_status_t status;
1376
1377 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1378 check_snd_dev_idle();
1379
1380 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00001381}
1382
1383
1384/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001385 * Establish unidirectional media flow from souce to sink.
1386 */
1387PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1388 pjsua_conf_port_id sink)
1389{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001390 /* If sound device idle timer is active, cancel it first. */
1391 if (pjsua_var.snd_idle_timer.id) {
1392 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
1393 pjsua_var.snd_idle_timer.id = PJ_FALSE;
1394 }
1395
1396 /* Create sound port if none is instantiated */
1397 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1398 !pjsua_var.no_snd)
1399 {
1400 pj_status_t status;
1401
1402 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1403 if (status != PJ_SUCCESS) {
1404 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1405 return status;
1406 }
1407 }
1408
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001409 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
1410}
1411
1412
1413/*
1414 * Disconnect media flow from the source to destination port.
1415 */
1416PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
1417 pjsua_conf_port_id sink)
1418{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001419 pj_status_t status;
1420
1421 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001422 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001423
1424 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001425}
1426
1427
Benny Prijono6dd967c2006-12-26 02:27:14 +00001428/*
1429 * Adjust the signal level to be transmitted from the bridge to the
1430 * specified port by making it louder or quieter.
1431 */
1432PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
1433 float level)
1434{
1435 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
1436 (int)((level-1) * 128));
1437}
1438
1439/*
1440 * Adjust the signal level to be received from the specified port (to
1441 * the bridge) by making it louder or quieter.
1442 */
1443PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
1444 float level)
1445{
1446 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
1447 (int)((level-1) * 128));
1448}
1449
1450
1451/*
1452 * Get last signal level transmitted to or received from the specified port.
1453 */
1454PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
1455 unsigned *tx_level,
1456 unsigned *rx_level)
1457{
1458 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
1459 tx_level, rx_level);
1460}
1461
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001462/*****************************************************************************
1463 * File player.
1464 */
1465
Benny Prijonod5696da2007-07-17 16:25:45 +00001466static char* get_basename(const char *path, unsigned len)
1467{
1468 char *p = ((char*)path) + len;
1469
1470 if (len==0)
1471 return p;
1472
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001473 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00001474
1475 return (p==path) ? p : p+1;
1476}
1477
1478
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001479/*
1480 * Create a file player, and automatically connect this player to
1481 * the conference bridge.
1482 */
1483PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1484 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001485 pjsua_player_id *p_id)
1486{
1487 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001488 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00001489 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001490 pjmedia_port *port;
1491 pj_status_t status;
1492
1493 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1494 return PJ_ETOOMANY;
1495
1496 PJSUA_LOCK();
1497
1498 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1499 if (pjsua_var.player[file_id].port == NULL)
1500 break;
1501 }
1502
1503 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1504 /* This is unexpected */
1505 PJSUA_UNLOCK();
1506 pj_assert(0);
1507 return PJ_EBUG;
1508 }
1509
1510 pj_memcpy(path, filename->ptr, filename->slen);
1511 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00001512
1513 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1514 if (!pool) {
1515 PJSUA_UNLOCK();
1516 return PJ_ENOMEM;
1517 }
1518
1519 status = pjmedia_wav_player_port_create(pool, path,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001520 pjsua_var.mconf_cfg.samples_per_frame *
1521 1000 / pjsua_var.media_cfg.clock_rate,
Benny Prijono00cae612006-07-31 15:19:36 +00001522 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001523 if (status != PJ_SUCCESS) {
1524 PJSUA_UNLOCK();
1525 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001526 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001527 return status;
1528 }
1529
Benny Prijono5297af92008-03-18 13:40:40 +00001530 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001531 port, filename, &slot);
1532 if (status != PJ_SUCCESS) {
1533 pjmedia_port_destroy(port);
1534 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00001535 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1536 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001537 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001538 return status;
1539 }
1540
Benny Prijonoa66c3312007-01-21 23:12:40 +00001541 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00001542 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001543 pjsua_var.player[file_id].port = port;
1544 pjsua_var.player[file_id].slot = slot;
1545
1546 if (p_id) *p_id = file_id;
1547
1548 ++pjsua_var.player_cnt;
1549
1550 PJSUA_UNLOCK();
1551 return PJ_SUCCESS;
1552}
1553
1554
1555/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00001556 * Create a file playlist media port, and automatically add the port
1557 * to the conference bridge.
1558 */
1559PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
1560 unsigned file_count,
1561 const pj_str_t *label,
1562 unsigned options,
1563 pjsua_player_id *p_id)
1564{
1565 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00001566 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001567 pjmedia_port *port;
1568 pj_status_t status;
1569
1570 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1571 return PJ_ETOOMANY;
1572
1573 PJSUA_LOCK();
1574
1575 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1576 if (pjsua_var.player[file_id].port == NULL)
1577 break;
1578 }
1579
1580 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1581 /* This is unexpected */
1582 PJSUA_UNLOCK();
1583 pj_assert(0);
1584 return PJ_EBUG;
1585 }
1586
1587
1588 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
1589 pjsua_var.media_cfg.clock_rate;
1590
Benny Prijonod5696da2007-07-17 16:25:45 +00001591 pool = pjsua_pool_create("playlist", 1000, 1000);
1592 if (!pool) {
1593 PJSUA_UNLOCK();
1594 return PJ_ENOMEM;
1595 }
1596
1597 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001598 file_names, file_count,
1599 ptime, options, 0, &port);
1600 if (status != PJ_SUCCESS) {
1601 PJSUA_UNLOCK();
1602 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001603 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001604 return status;
1605 }
1606
Benny Prijonod5696da2007-07-17 16:25:45 +00001607 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001608 port, &port->info.name, &slot);
1609 if (status != PJ_SUCCESS) {
1610 pjmedia_port_destroy(port);
1611 PJSUA_UNLOCK();
1612 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001613 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001614 return status;
1615 }
1616
1617 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00001618 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001619 pjsua_var.player[file_id].port = port;
1620 pjsua_var.player[file_id].slot = slot;
1621
1622 if (p_id) *p_id = file_id;
1623
1624 ++pjsua_var.player_cnt;
1625
1626 PJSUA_UNLOCK();
1627 return PJ_SUCCESS;
1628
1629}
1630
1631
1632/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001633 * Get conference port ID associated with player.
1634 */
1635PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
1636{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001637 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001638 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1639
1640 return pjsua_var.player[id].slot;
1641}
1642
Benny Prijono469b1522006-12-26 03:05:17 +00001643/*
1644 * Get the media port for the player.
1645 */
Benny Prijonobe41d862008-01-18 13:24:28 +00001646PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00001647 pjmedia_port **p_port)
1648{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001649 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001650 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1651 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1652
1653 *p_port = pjsua_var.player[id].port;
1654
1655 return PJ_SUCCESS;
1656}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001657
1658/*
1659 * Set playback position.
1660 */
1661PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
1662 pj_uint32_t samples)
1663{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001664 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001665 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001666 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001667
1668 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
1669}
1670
1671
1672/*
1673 * Close the file, remove the player from the bridge, and free
1674 * resources associated with the file player.
1675 */
1676PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
1677{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001678 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001679 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1680
1681 PJSUA_LOCK();
1682
1683 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001684 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001685 pjmedia_port_destroy(pjsua_var.player[id].port);
1686 pjsua_var.player[id].port = NULL;
1687 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00001688 pj_pool_release(pjsua_var.player[id].pool);
1689 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001690 pjsua_var.player_cnt--;
1691 }
1692
1693 PJSUA_UNLOCK();
1694
1695 return PJ_SUCCESS;
1696}
1697
1698
1699/*****************************************************************************
1700 * File recorder.
1701 */
1702
1703/*
1704 * Create a file recorder, and automatically connect this recorder to
1705 * the conference bridge.
1706 */
1707PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00001708 unsigned enc_type,
1709 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001710 pj_ssize_t max_size,
1711 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001712 pjsua_recorder_id *p_id)
1713{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001714 enum Format
1715 {
1716 FMT_UNKNOWN,
1717 FMT_WAV,
1718 FMT_MP3,
1719 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001720 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001721 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001722 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00001723 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00001724 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001725 pjmedia_port *port;
1726 pj_status_t status;
1727
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001728 /* Filename must present */
1729 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
1730
Benny Prijono00cae612006-07-31 15:19:36 +00001731 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001732 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001733
Benny Prijono8f310522006-10-20 11:08:49 +00001734 /* Don't support encoding type at present */
1735 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001736
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001737 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
1738 return PJ_ETOOMANY;
1739
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001740 /* Determine the file format */
1741 ext.ptr = filename->ptr + filename->slen - 4;
1742 ext.slen = 4;
1743
1744 if (pj_stricmp2(&ext, ".wav") == 0)
1745 file_format = FMT_WAV;
1746 else if (pj_stricmp2(&ext, ".mp3") == 0)
1747 file_format = FMT_MP3;
1748 else {
1749 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
1750 "determine file format for %.*s",
1751 (int)filename->slen, filename->ptr));
1752 return PJ_ENOTSUP;
1753 }
1754
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001755 PJSUA_LOCK();
1756
1757 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
1758 if (pjsua_var.recorder[file_id].port == NULL)
1759 break;
1760 }
1761
1762 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
1763 /* This is unexpected */
1764 PJSUA_UNLOCK();
1765 pj_assert(0);
1766 return PJ_EBUG;
1767 }
1768
1769 pj_memcpy(path, filename->ptr, filename->slen);
1770 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001771
Benny Prijonod5696da2007-07-17 16:25:45 +00001772 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1773 if (!pool) {
1774 PJSUA_UNLOCK();
1775 return PJ_ENOMEM;
1776 }
1777
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001778 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00001779 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001780 pjsua_var.media_cfg.clock_rate,
1781 pjsua_var.mconf_cfg.channel_count,
1782 pjsua_var.mconf_cfg.samples_per_frame,
1783 pjsua_var.mconf_cfg.bits_per_sample,
1784 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001785 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00001786 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001787 port = NULL;
1788 status = PJ_ENOTSUP;
1789 }
1790
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001791 if (status != PJ_SUCCESS) {
1792 PJSUA_UNLOCK();
1793 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001794 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001795 return status;
1796 }
1797
Benny Prijonod5696da2007-07-17 16:25:45 +00001798 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001799 port, filename, &slot);
1800 if (status != PJ_SUCCESS) {
1801 pjmedia_port_destroy(port);
1802 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00001803 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001804 return status;
1805 }
1806
1807 pjsua_var.recorder[file_id].port = port;
1808 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00001809 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001810
1811 if (p_id) *p_id = file_id;
1812
1813 ++pjsua_var.rec_cnt;
1814
1815 PJSUA_UNLOCK();
1816 return PJ_SUCCESS;
1817}
1818
1819
1820/*
1821 * Get conference port associated with recorder.
1822 */
1823PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
1824{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001825 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1826 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001827 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1828
1829 return pjsua_var.recorder[id].slot;
1830}
1831
Benny Prijono469b1522006-12-26 03:05:17 +00001832/*
1833 * Get the media port for the recorder.
1834 */
1835PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
1836 pjmedia_port **p_port)
1837{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001838 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1839 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001840 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1841 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1842
1843 *p_port = pjsua_var.recorder[id].port;
1844 return PJ_SUCCESS;
1845}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001846
1847/*
1848 * Destroy recorder (this will complete recording).
1849 */
1850PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
1851{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001852 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1853 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001854 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1855
1856 PJSUA_LOCK();
1857
1858 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001859 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001860 pjmedia_port_destroy(pjsua_var.recorder[id].port);
1861 pjsua_var.recorder[id].port = NULL;
1862 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00001863 pj_pool_release(pjsua_var.recorder[id].pool);
1864 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001865 pjsua_var.rec_cnt--;
1866 }
1867
1868 PJSUA_UNLOCK();
1869
1870 return PJ_SUCCESS;
1871}
1872
1873
1874/*****************************************************************************
1875 * Sound devices.
1876 */
1877
1878/*
1879 * Enum sound devices.
1880 */
1881PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
1882 unsigned *count)
1883{
1884 unsigned i, dev_count;
1885
1886 dev_count = pjmedia_snd_get_dev_count();
1887
1888 if (dev_count > *count) dev_count = *count;
1889
1890 for (i=0; i<dev_count; ++i) {
1891 const pjmedia_snd_dev_info *ci;
1892
1893 ci = pjmedia_snd_get_dev_info(i);
1894 pj_memcpy(&info[i], ci, sizeof(*ci));
1895 }
1896
1897 *count = dev_count;
1898
1899 return PJ_SUCCESS;
1900}
1901
1902
1903/* Close existing sound device */
1904static void close_snd_dev(void)
1905{
1906 /* Close sound device */
1907 if (pjsua_var.snd_port) {
1908 const pjmedia_snd_dev_info *cap_info, *play_info;
1909
1910 cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
1911 play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
1912
1913 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
1914 "%s sound capture device",
1915 play_info->name, cap_info->name));
1916
1917 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
1918 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1919 pjsua_var.snd_port = NULL;
1920 }
1921
1922 /* Close null sound device */
1923 if (pjsua_var.null_snd) {
1924 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
1925 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
1926 pjsua_var.null_snd = NULL;
1927 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001928
1929 if (pjsua_var.snd_pool)
1930 pj_pool_release(pjsua_var.snd_pool);
1931 pjsua_var.snd_pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001932}
1933
1934/*
1935 * Select or change sound device. Application may call this function at
1936 * any time to replace current sound device.
1937 */
1938PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
1939 int playback_dev)
1940{
1941 pjmedia_port *conf_port;
Benny Prijono6dd967c2006-12-26 02:27:14 +00001942 const pjmedia_snd_dev_info *play_info;
Benny Prijonof3758ee2008-02-26 15:32:16 +00001943 unsigned clock_rates[] = {0, 22050, 44100, 48000, 32000, 16000,
1944 8000};
Benny Prijono658a1c52006-10-11 21:56:16 +00001945 unsigned selected_clock_rate = 0;
Benny Prijono26056d82006-10-11 16:03:41 +00001946 unsigned i;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00001947 pjmedia_snd_stream *strm;
1948 pjmedia_snd_stream_info si;
1949 pj_str_t tmp;
Benny Prijono26056d82006-10-11 16:03:41 +00001950 pj_status_t status = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001951
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001952 /* Check if NULL sound device is used */
1953 if (NULL_SND_DEV_ID == capture_dev || NULL_SND_DEV_ID == playback_dev) {
1954 return pjsua_set_null_snd_dev();
1955 }
1956
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001957 /* Close existing sound port */
1958 close_snd_dev();
1959
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001960 /* Create memory pool for sound device. */
1961 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
1962 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001963
Benny Prijono26056d82006-10-11 16:03:41 +00001964 /* Set default clock rate */
Benny Prijonof3758ee2008-02-26 15:32:16 +00001965 clock_rates[0] = pjsua_var.media_cfg.snd_clock_rate;
1966 if (clock_rates[0] == 0)
1967 clock_rates[0] = pjsua_var.media_cfg.clock_rate;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001968
Benny Prijono50f19b32008-03-11 13:15:43 +00001969 /* Get the port0 of the conference bridge. */
1970 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1971 pj_assert(conf_port != NULL);
1972
Benny Prijono26056d82006-10-11 16:03:41 +00001973 /* Attempts to open the sound device with different clock rates */
1974 for (i=0; i<PJ_ARRAY_SIZE(clock_rates); ++i) {
1975 char errmsg[PJ_ERR_MSG_SIZE];
Benny Prijonocf0b4b22007-10-06 17:31:09 +00001976 unsigned fps;
Benny Prijono26056d82006-10-11 16:03:41 +00001977
1978 PJ_LOG(4,(THIS_FILE,
1979 "pjsua_set_snd_dev(): attempting to open devices "
1980 "@%d Hz", clock_rates[i]));
1981
1982 /* Create the sound device. Sound port will start immediately. */
Benny Prijonocf0b4b22007-10-06 17:31:09 +00001983 fps = 1000 / pjsua_var.media_cfg.audio_frame_ptime;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001984 status = pjmedia_snd_port_create(pjsua_var.snd_pool, capture_dev,
Benny Prijono26056d82006-10-11 16:03:41 +00001985 playback_dev,
Benny Prijono7d60d052008-03-29 12:24:20 +00001986 clock_rates[i],
1987 pjsua_var.media_cfg.channel_count,
1988 clock_rates[i]/fps *
1989 pjsua_var.media_cfg.channel_count,
Benny Prijono26056d82006-10-11 16:03:41 +00001990 16, 0, &pjsua_var.snd_port);
1991
Benny Prijono658a1c52006-10-11 21:56:16 +00001992 if (status == PJ_SUCCESS) {
1993 selected_clock_rate = clock_rates[i];
Benny Prijono50f19b32008-03-11 13:15:43 +00001994
1995 /* If there's mismatch between sound port and conference's port,
1996 * create a resample port to bridge them.
1997 */
1998 if (selected_clock_rate != pjsua_var.media_cfg.clock_rate) {
1999 pjmedia_port *resample_port;
2000 unsigned resample_opt = 0;
2001
2002 if (pjsua_var.media_cfg.quality >= 3 &&
2003 pjsua_var.media_cfg.quality <= 4)
2004 {
Nanang Izzuddine85a1832008-05-29 11:03:23 +00002005 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
Benny Prijono50f19b32008-03-11 13:15:43 +00002006 }
2007 else if (pjsua_var.media_cfg.quality < 3) {
Nanang Izzuddine85a1832008-05-29 11:03:23 +00002008 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
Benny Prijono50f19b32008-03-11 13:15:43 +00002009 }
2010
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002011 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
Benny Prijono50f19b32008-03-11 13:15:43 +00002012 conf_port,
2013 selected_clock_rate,
2014 resample_opt,
2015 &resample_port);
2016 if (status != PJ_SUCCESS) {
2017 pj_strerror(status, errmsg, sizeof(errmsg));
2018 PJ_LOG(4, (THIS_FILE,
2019 "Error creating resample port, trying next "
2020 "clock rate",
2021 errmsg));
2022 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2023 pjsua_var.snd_port = NULL;
2024 continue;
2025 } else {
2026 conf_port = resample_port;
2027 break;
2028 }
2029
2030 } else {
2031 break;
2032 }
Benny Prijono658a1c52006-10-11 21:56:16 +00002033 }
Benny Prijono26056d82006-10-11 16:03:41 +00002034
2035 pj_strerror(status, errmsg, sizeof(errmsg));
Benny Prijono658a1c52006-10-11 21:56:16 +00002036 PJ_LOG(4, (THIS_FILE, "..failed: %s", errmsg));
Benny Prijono26056d82006-10-11 16:03:41 +00002037 }
2038
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002039 if (status != PJ_SUCCESS) {
2040 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
2041 return status;
2042 }
2043
Benny Prijonof20687a2006-08-04 18:27:19 +00002044 /* Set AEC */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002045 pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.snd_pool,
Benny Prijono5da50432006-08-07 10:24:52 +00002046 pjsua_var.media_cfg.ec_tail_len,
2047 pjsua_var.media_cfg.ec_options);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002048
Benny Prijono52a93912006-08-04 20:54:37 +00002049 /* Connect sound port to the bridge */
2050 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
2051 conf_port );
2052 if (status != PJ_SUCCESS) {
2053 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
2054 "sound device", status);
2055 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2056 pjsua_var.snd_port = NULL;
2057 return status;
2058 }
2059
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002060 /* Save the device IDs */
2061 pjsua_var.cap_dev = capture_dev;
2062 pjsua_var.play_dev = playback_dev;
2063
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002064 /* Update sound device name. */
2065 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2066 pjmedia_snd_stream_get_info(strm, &si);
2067 play_info = pjmedia_snd_get_dev_info(si.rec_id);
2068
Benny Prijonof3758ee2008-02-26 15:32:16 +00002069 if (si.clock_rate != pjsua_var.media_cfg.clock_rate) {
2070 char tmp_buf[128];
2071 int tmp_buf_len = sizeof(tmp_buf);
2072
2073 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1, "%s (%dKHz)",
2074 play_info->name, si.clock_rate/1000);
2075 pj_strset(&tmp, tmp_buf, tmp_buf_len);
2076 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
2077 } else {
2078 pjmedia_conf_set_port0_name(pjsua_var.mconf,
2079 pj_cstr(&tmp, play_info->name));
2080 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002081
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002082 return PJ_SUCCESS;
2083}
2084
2085
2086/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00002087 * Get currently active sound devices. If sound devices has not been created
2088 * (for example when pjsua_start() is not called), it is possible that
2089 * the function returns PJ_SUCCESS with -1 as device IDs.
2090 */
2091PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
2092 int *playback_dev)
2093{
2094 if (capture_dev) {
2095 *capture_dev = pjsua_var.cap_dev;
2096 }
2097 if (playback_dev) {
2098 *playback_dev = pjsua_var.play_dev;
2099 }
2100
2101 return PJ_SUCCESS;
2102}
2103
2104
2105/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002106 * Use null sound device.
2107 */
2108PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
2109{
2110 pjmedia_port *conf_port;
2111 pj_status_t status;
2112
2113 /* Close existing sound device */
2114 close_snd_dev();
2115
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002116 /* Create memory pool for sound device. */
2117 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2118 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2119
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002120 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
2121
2122 /* Get the port0 of the conference bridge. */
2123 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2124 pj_assert(conf_port != NULL);
2125
2126 /* Create master port, connecting port0 of the conference bridge to
2127 * a null port.
2128 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002129 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002130 conf_port, 0, &pjsua_var.null_snd);
2131 if (status != PJ_SUCCESS) {
2132 pjsua_perror(THIS_FILE, "Unable to create null sound device",
2133 status);
2134 return status;
2135 }
2136
2137 /* Start the master port */
2138 status = pjmedia_master_port_start(pjsua_var.null_snd);
2139 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2140
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002141 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2142 pjsua_var.play_dev = NULL_SND_DEV_ID;
2143
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002144 return PJ_SUCCESS;
2145}
2146
2147
Benny Prijonoe909eac2006-07-27 22:04:56 +00002148
2149/*
2150 * Use no device!
2151 */
2152PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2153{
2154 /* Close existing sound device */
2155 close_snd_dev();
2156
2157 pjsua_var.no_snd = PJ_TRUE;
2158 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2159}
2160
2161
Benny Prijonof20687a2006-08-04 18:27:19 +00002162/*
2163 * Configure the AEC settings of the sound port.
2164 */
Benny Prijono5da50432006-08-07 10:24:52 +00002165PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00002166{
2167 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2168
2169 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00002170 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
2171 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00002172
2173 return PJ_SUCCESS;
2174}
2175
2176
2177/*
2178 * Get current AEC tail length.
2179 */
Benny Prijono22dfe592006-08-06 12:07:13 +00002180PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00002181{
2182 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2183 return PJ_SUCCESS;
2184}
2185
Benny Prijonoe909eac2006-07-27 22:04:56 +00002186
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002187/*****************************************************************************
2188 * Codecs.
2189 */
2190
2191/*
2192 * Enum all supported codecs in the system.
2193 */
2194PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
2195 unsigned *p_count )
2196{
2197 pjmedia_codec_mgr *codec_mgr;
2198 pjmedia_codec_info info[32];
2199 unsigned i, count, prio[32];
2200 pj_status_t status;
2201
2202 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2203 count = PJ_ARRAY_SIZE(info);
2204 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
2205 if (status != PJ_SUCCESS) {
2206 *p_count = 0;
2207 return status;
2208 }
2209
2210 if (count > *p_count) count = *p_count;
2211
2212 for (i=0; i<count; ++i) {
2213 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
2214 id[i].codec_id = pj_str(id[i].buf_);
2215 id[i].priority = (pj_uint8_t) prio[i];
2216 }
2217
2218 *p_count = count;
2219
2220 return PJ_SUCCESS;
2221}
2222
2223
2224/*
2225 * Change codec priority.
2226 */
2227PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
2228 pj_uint8_t priority )
2229{
2230 pjmedia_codec_mgr *codec_mgr;
2231
2232 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2233
2234 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
2235 priority);
2236}
2237
2238
2239/*
2240 * Get codec parameters.
2241 */
2242PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
2243 pjmedia_codec_param *param )
2244{
2245 const pjmedia_codec_info *info;
2246 pjmedia_codec_mgr *codec_mgr;
2247 unsigned count = 1;
2248 pj_status_t status;
2249
2250 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2251
2252 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
2253 &count, &info, NULL);
2254 if (status != PJ_SUCCESS)
2255 return status;
2256
2257 if (count != 1)
2258 return PJ_ENOTFOUND;
2259
2260 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
2261 return status;
2262}
2263
2264
2265/*
2266 * Set codec parameters.
2267 */
2268PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
2269 const pjmedia_codec_param *param)
2270{
Benny Prijono00cae612006-07-31 15:19:36 +00002271 PJ_UNUSED_ARG(id);
2272 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002273 PJ_TODO(set_codec_param);
2274 return PJ_SUCCESS;
2275}