blob: 84b85101169093e972468004ea50e7ffa4c2612d [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;
Nanang Izzuddin4375f902008-06-26 19:12:09 +0000891 /* If media session has been ever established, let's use remote's
892 * preference in SRTP usage policy, especially when it is stricter.
893 */
894 if (call->rem_srtp_use > acc->cfg.use_srtp)
895 srtp_opt.use = call->rem_srtp_use;
896 else
897 srtp_opt.use = acc->cfg.use_srtp;
898
Benny Prijono53a7c702008-04-14 02:57:29 +0000899 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
900 call->med_tp,
901 &srtp_opt, &srtp);
902 if (status != PJ_SUCCESS) {
903 if (sip_err_code)
904 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
905 return status;
906 }
Benny Prijonod8179652008-01-23 20:39:07 +0000907
Benny Prijono53a7c702008-04-14 02:57:29 +0000908 /* Set SRTP as current media transport */
909 call->med_orig = call->med_tp;
910 call->med_tp = srtp;
911 }
Benny Prijonod8179652008-01-23 20:39:07 +0000912#else
913 call->med_orig = call->med_tp;
914 PJ_UNUSED_ARG(security_level);
915#endif
916
Benny Prijono224b4e22008-06-19 14:10:28 +0000917 /* Create the media transport */
918 status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0,
919 rem_sdp, MEDIA_IDX);
920 if (status != PJ_SUCCESS) {
921 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
922 pjsua_media_channel_deinit(call_id);
923 return status;
924 }
925
926 call->med_tp_st = PJSUA_MED_TP_INIT;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000927 return PJ_SUCCESS;
928}
929
930pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
931 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +0000932 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000933 pjmedia_sdp_session **p_sdp,
934 int *sip_status_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000935{
Benny Prijonod8179652008-01-23 20:39:07 +0000936 enum { MAX_MEDIA = 1, MEDIA_IDX = 0 };
Benny Prijonoc97608e2007-03-23 16:34:20 +0000937 pjmedia_sdp_session *sdp;
Benny Prijonoe1a5a852008-03-11 21:38:05 +0000938 pjmedia_transport_info tpinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000939 pjsua_call *call = &pjsua_var.calls[call_id];
940 pj_status_t status;
941
Benny Prijono55e82352007-05-10 20:49:08 +0000942 /* Return error if media transport has not been created yet
943 * (e.g. application is starting)
944 */
945 if (call->med_tp == NULL) {
946 return PJ_EBUSY;
947 }
948
Benny Prijono224b4e22008-06-19 14:10:28 +0000949 /* Create media if it's not created. This could happen when call is
950 * currently on-hold
951 */
952 if (call->med_tp_st == PJSUA_MED_TP_IDLE) {
953 pjsip_role_e role;
954 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
955 status = pjsua_media_channel_init(call_id, role, call->secure_level,
956 pool, rem_sdp, sip_status_code);
957 if (status != PJ_SUCCESS)
958 return status;
959 }
960
Benny Prijono617c5bc2007-04-02 19:51:21 +0000961 /* Get media socket info */
Benny Prijono734fc2d2008-03-17 16:05:35 +0000962 pjmedia_transport_info_init(&tpinfo);
Benny Prijonoe1a5a852008-03-11 21:38:05 +0000963 pjmedia_transport_get_info(call->med_tp, &tpinfo);
Benny Prijono617c5bc2007-04-02 19:51:21 +0000964
965 /* Create SDP */
Benny Prijonod8179652008-01-23 20:39:07 +0000966 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA,
Benny Prijonoe1a5a852008-03-11 21:38:05 +0000967 &tpinfo.sock_info, &sdp);
Benny Prijono25b2ea12008-01-24 19:20:54 +0000968 if (status != PJ_SUCCESS) {
969 if (sip_status_code) *sip_status_code = 500;
Benny Prijono224b4e22008-06-19 14:10:28 +0000970 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +0000971 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000972
Benny Prijono6ba8c542007-10-16 01:34:14 +0000973 /* Add NAT info in the SDP */
974 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
975 pjmedia_sdp_attr *a;
976 pj_str_t value;
977 char nat_info[80];
978
979 value.ptr = nat_info;
980 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
981 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
982 "%d", pjsua_var.nat_type);
983 } else {
984 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
985 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
986 "%d %s",
987 pjsua_var.nat_type,
988 type_name);
989 }
990
991 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
992
993 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
994
995 }
996
Benny Prijonod8179652008-01-23 20:39:07 +0000997 /* Give the SDP to media transport */
Benny Prijono224b4e22008-06-19 14:10:28 +0000998 status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp,
999 MEDIA_IDX);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001000 if (status != PJ_SUCCESS) {
1001 if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono224b4e22008-06-19 14:10:28 +00001002 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001003 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001004
1005 *p_sdp = sdp;
1006 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001007}
1008
1009
1010static void stop_media_session(pjsua_call_id call_id)
1011{
1012 pjsua_call *call = &pjsua_var.calls[call_id];
1013
1014 if (call->conf_slot != PJSUA_INVALID_ID) {
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001015 if (pjsua_var.mconf) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001016 pjsua_conf_remove_port(call->conf_slot);
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001017 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001018 call->conf_slot = PJSUA_INVALID_ID;
1019 }
1020
1021 if (call->session) {
Benny Prijonofc13bf62008-02-20 08:56:15 +00001022 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1023 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0);
1024 }
1025
Benny Prijonoc97608e2007-03-23 16:34:20 +00001026 pjmedia_session_destroy(call->session);
1027 call->session = NULL;
1028
1029 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
1030 call_id));
1031
1032 }
1033
1034 call->media_st = PJSUA_CALL_MEDIA_NONE;
1035}
1036
1037pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1038{
1039 pjsua_call *call = &pjsua_var.calls[call_id];
1040
1041 stop_media_session(call_id);
1042
Benny Prijono224b4e22008-06-19 14:10:28 +00001043 if (call->med_tp_st != PJSUA_MED_TP_IDLE) {
1044 pjmedia_transport_media_stop(call->med_tp);
1045 call->med_tp_st = PJSUA_MED_TP_IDLE;
1046 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001047
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001048 if (call->med_orig && call->med_tp != call->med_orig) {
Benny Prijonod8179652008-01-23 20:39:07 +00001049 pjmedia_transport_close(call->med_tp);
1050 call->med_tp = call->med_orig;
1051 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001052 return PJ_SUCCESS;
1053}
1054
1055
1056/*
1057 * DTMF callback from the stream.
1058 */
1059static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1060 int digit)
1061{
1062 PJ_UNUSED_ARG(strm);
1063
Benny Prijono0c068262008-02-14 14:38:52 +00001064 /* For discussions about call mutex protection related to this
1065 * callback, please see ticket #460:
1066 * http://trac.pjsip.org/repos/ticket/460#comment:4
1067 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001068 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1069 pjsua_call_id call_id;
1070
Benny Prijonod8179652008-01-23 20:39:07 +00001071 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001072 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1073 }
1074}
1075
1076
1077pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijono224b4e22008-06-19 14:10:28 +00001078 const pjmedia_sdp_session *local_sdp,
Benny Prijonodbce2cf2007-03-28 16:24:00 +00001079 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001080{
Benny Prijono91e567e2007-12-28 08:51:58 +00001081 unsigned i;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001082 int prev_media_st = 0;
1083 pjsua_call *call = &pjsua_var.calls[call_id];
1084 pjmedia_session_info sess_info;
Benny Prijono91e567e2007-12-28 08:51:58 +00001085 pjmedia_stream_info *si = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001086 pjmedia_port *media_port;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001087 pj_status_t status;
1088
1089 /* Destroy existing media session, if any. */
1090 prev_media_st = call->media_st;
1091 stop_media_session(call->index);
1092
1093 /* Create media session info based on SDP parameters.
Benny Prijonoc97608e2007-03-23 16:34:20 +00001094 */
1095 status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
1096 pjsua_var.med_endpt,
Benny Prijono91e567e2007-12-28 08:51:58 +00001097 PJMEDIA_MAX_SDP_MEDIA, &sess_info,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001098 local_sdp, remote_sdp);
1099 if (status != PJ_SUCCESS)
1100 return status;
1101
Benny Prijono91e567e2007-12-28 08:51:58 +00001102 /* Find which session is audio (we only support audio for now) */
1103 for (i=0; i < sess_info.stream_cnt; ++i) {
1104 if (sess_info.stream_info[i].type == PJMEDIA_TYPE_AUDIO &&
Benny Prijonod8179652008-01-23 20:39:07 +00001105 (sess_info.stream_info[i].proto == PJMEDIA_TP_PROTO_RTP_AVP ||
1106 sess_info.stream_info[i].proto == PJMEDIA_TP_PROTO_RTP_SAVP))
Benny Prijono91e567e2007-12-28 08:51:58 +00001107 {
1108 si = &sess_info.stream_info[i];
1109 break;
1110 }
1111 }
1112
1113 if (si == NULL) {
1114 /* Not found */
1115 return PJMEDIA_EINVALIMEDIATYPE;
1116 }
1117
1118
1119 /* Reset session info with only one media stream */
1120 sess_info.stream_cnt = 1;
1121 if (si != &sess_info.stream_info[0])
1122 pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001123
1124 /* Check if media is put on-hold */
Benny Prijono91e567e2007-12-28 08:51:58 +00001125 if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001126 {
1127
1128 /* Determine who puts the call on-hold */
1129 if (prev_media_st == PJSUA_CALL_MEDIA_ACTIVE) {
1130 if (pjmedia_sdp_neg_was_answer_remote(call->inv->neg)) {
1131 /* It was local who offer hold */
1132 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
1133 } else {
1134 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
1135 }
1136 }
1137
1138 call->media_dir = PJMEDIA_DIR_NONE;
1139
Benny Prijonod8179652008-01-23 20:39:07 +00001140 /* Shutdown transport's session */
1141 pjmedia_transport_media_stop(call->med_tp);
Benny Prijono224b4e22008-06-19 14:10:28 +00001142 call->med_tp_st = PJSUA_MED_TP_IDLE;
Benny Prijono667952e2007-04-02 19:27:54 +00001143
Benny Prijonoc97608e2007-03-23 16:34:20 +00001144 /* No need because we need keepalive? */
1145
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001146 /* Close upper entry of transport stack */
1147 if (call->med_orig && (call->med_tp != call->med_orig)) {
1148 pjmedia_transport_close(call->med_tp);
1149 call->med_tp = call->med_orig;
1150 }
1151
Benny Prijonoc97608e2007-03-23 16:34:20 +00001152 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001153 pjmedia_srtp_info srtp_info;
1154 pjmedia_transport_info tp_info;
1155
Benny Prijono224b4e22008-06-19 14:10:28 +00001156 /* Start/restart media transport */
Benny Prijonod8179652008-01-23 20:39:07 +00001157 status = pjmedia_transport_media_start(call->med_tp,
1158 call->inv->pool,
1159 local_sdp, remote_sdp, 0);
1160 if (status != PJ_SUCCESS)
1161 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001162
Benny Prijono224b4e22008-06-19 14:10:28 +00001163 call->med_tp_st = PJSUA_MED_TP_RUNNING;
1164
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001165 /* Get remote SRTP usage policy */
1166 pjmedia_transport_info_init(&tp_info);
1167 pjmedia_transport_get_info(call->med_tp, &tp_info);
1168 if (tp_info.specific_info_cnt > 0) {
1169 int i;
1170 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1171 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1172 {
1173 pjmedia_srtp_info *srtp_info =
1174 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1175
1176 call->rem_srtp_use = srtp_info->peer_use;
1177 break;
1178 }
1179 }
1180 }
1181
Benny Prijonoc97608e2007-03-23 16:34:20 +00001182 /* Override ptime, if this option is specified. */
1183 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001184 si->param->setting.frm_per_pkt = (pj_uint8_t)
1185 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1186 if (si->param->setting.frm_per_pkt == 0)
1187 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001188 }
1189
1190 /* Disable VAD, if this option is specified. */
1191 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001192 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001193 }
1194
1195
1196 /* Optionally, application may modify other stream settings here
1197 * (such as jitter buffer parameters, codec ptime, etc.)
1198 */
Benny Prijono91e567e2007-12-28 08:51:58 +00001199 si->jb_init = pjsua_var.media_cfg.jb_init;
1200 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1201 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1202 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001203
Benny Prijono8147f402007-11-21 14:50:07 +00001204 /* Set SSRC */
Benny Prijono91e567e2007-12-28 08:51:58 +00001205 si->ssrc = call->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00001206
Benny Prijonoc97608e2007-03-23 16:34:20 +00001207 /* Create session based on session info. */
1208 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
1209 &call->med_tp,
1210 call, &call->session );
1211 if (status != PJ_SUCCESS) {
1212 return status;
1213 }
1214
1215 /* If DTMF callback is installed by application, install our
1216 * callback to the session.
1217 */
1218 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1219 pjmedia_session_set_dtmf_callback(call->session, 0,
1220 &dtmf_callback,
Benny Prijonod8179652008-01-23 20:39:07 +00001221 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001222 }
1223
1224 /* Get the port interface of the first stream in the session.
1225 * We need the port interface to add to the conference bridge.
1226 */
1227 pjmedia_session_get_port(call->session, 0, &media_port);
1228
Benny Prijonofc13bf62008-02-20 08:56:15 +00001229 /* Notify application about stream creation.
1230 * Note: application may modify media_port to point to different
1231 * media port
1232 */
1233 if (pjsua_var.ua_cfg.cb.on_stream_created) {
1234 pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session,
1235 0, &media_port);
1236 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001237
1238 /*
1239 * Add the call to conference bridge.
1240 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001241 {
1242 char tmp[PJSIP_MAX_URL_SIZE];
1243 pj_str_t port_name;
1244
1245 port_name.ptr = tmp;
1246 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1247 call->inv->dlg->remote.info->uri,
1248 tmp, sizeof(tmp));
1249 if (port_name.slen < 1) {
1250 port_name = pj_str("call");
1251 }
1252 status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
1253 media_port,
1254 &port_name,
1255 (unsigned*)&call->conf_slot);
1256 if (status != PJ_SUCCESS) {
1257 return status;
1258 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001259 }
1260
1261 /* Call's media state is active */
1262 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijono91e567e2007-12-28 08:51:58 +00001263 call->media_dir = si->dir;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001264 }
1265
1266 /* Print info. */
1267 {
1268 char info[80];
1269 int info_len = 0;
1270 unsigned i;
1271
1272 for (i=0; i<sess_info.stream_cnt; ++i) {
1273 int len;
1274 const char *dir;
1275 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1276
1277 switch (strm_info->dir) {
1278 case PJMEDIA_DIR_NONE:
1279 dir = "inactive";
1280 break;
1281 case PJMEDIA_DIR_ENCODING:
1282 dir = "sendonly";
1283 break;
1284 case PJMEDIA_DIR_DECODING:
1285 dir = "recvonly";
1286 break;
1287 case PJMEDIA_DIR_ENCODING_DECODING:
1288 dir = "sendrecv";
1289 break;
1290 default:
1291 dir = "unknown";
1292 break;
1293 }
1294 len = pj_ansi_sprintf( info+info_len,
1295 ", stream #%d: %.*s (%s)", i,
1296 (int)strm_info->fmt.encoding_name.slen,
1297 strm_info->fmt.encoding_name.ptr,
1298 dir);
1299 if (len > 0)
1300 info_len += len;
1301 }
1302 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1303 }
1304
1305 return PJ_SUCCESS;
1306}
1307
1308
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001309/*
1310 * Get maxinum number of conference ports.
1311 */
1312PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1313{
1314 return pjsua_var.media_cfg.max_media_ports;
1315}
1316
1317
1318/*
1319 * Get current number of active ports in the bridge.
1320 */
1321PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1322{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001323 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001324 unsigned count = PJ_ARRAY_SIZE(ports);
1325 pj_status_t status;
1326
1327 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1328 if (status != PJ_SUCCESS)
1329 count = 0;
1330
1331 return count;
1332}
1333
1334
1335/*
1336 * Enumerate all conference ports.
1337 */
1338PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1339 unsigned *count)
1340{
1341 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1342}
1343
1344
1345/*
1346 * Get information about the specified conference port
1347 */
1348PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1349 pjsua_conf_port_info *info)
1350{
1351 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001352 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001353 pj_status_t status;
1354
1355 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1356 if (status != PJ_SUCCESS)
1357 return status;
1358
Benny Prijonoac623b32006-07-03 15:19:31 +00001359 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001360 info->slot_id = id;
1361 info->name = cinfo.name;
1362 info->clock_rate = cinfo.clock_rate;
1363 info->channel_count = cinfo.channel_count;
1364 info->samples_per_frame = cinfo.samples_per_frame;
1365 info->bits_per_sample = cinfo.bits_per_sample;
1366
1367 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001368 info->listener_cnt = cinfo.listener_cnt;
1369 for (i=0; i<cinfo.listener_cnt; ++i) {
1370 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001371 }
1372
1373 return PJ_SUCCESS;
1374}
1375
1376
1377/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001378 * Add arbitrary media port to PJSUA's conference bridge.
1379 */
1380PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1381 pjmedia_port *port,
1382 pjsua_conf_port_id *p_id)
1383{
1384 pj_status_t status;
1385
1386 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1387 port, NULL, (unsigned*)p_id);
1388 if (status != PJ_SUCCESS) {
1389 if (p_id)
1390 *p_id = PJSUA_INVALID_ID;
1391 }
1392
1393 return status;
1394}
1395
1396
1397/*
1398 * Remove arbitrary slot from the conference bridge.
1399 */
1400PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1401{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001402 pj_status_t status;
1403
1404 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1405 check_snd_dev_idle();
1406
1407 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00001408}
1409
1410
1411/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001412 * Establish unidirectional media flow from souce to sink.
1413 */
1414PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1415 pjsua_conf_port_id sink)
1416{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001417 /* If sound device idle timer is active, cancel it first. */
1418 if (pjsua_var.snd_idle_timer.id) {
1419 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
1420 pjsua_var.snd_idle_timer.id = PJ_FALSE;
1421 }
1422
1423 /* Create sound port if none is instantiated */
1424 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1425 !pjsua_var.no_snd)
1426 {
1427 pj_status_t status;
1428
1429 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1430 if (status != PJ_SUCCESS) {
1431 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1432 return status;
1433 }
1434 }
1435
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001436 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
1437}
1438
1439
1440/*
1441 * Disconnect media flow from the source to destination port.
1442 */
1443PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
1444 pjsua_conf_port_id sink)
1445{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001446 pj_status_t status;
1447
1448 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001449 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001450
1451 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001452}
1453
1454
Benny Prijono6dd967c2006-12-26 02:27:14 +00001455/*
1456 * Adjust the signal level to be transmitted from the bridge to the
1457 * specified port by making it louder or quieter.
1458 */
1459PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
1460 float level)
1461{
1462 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
1463 (int)((level-1) * 128));
1464}
1465
1466/*
1467 * Adjust the signal level to be received from the specified port (to
1468 * the bridge) by making it louder or quieter.
1469 */
1470PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
1471 float level)
1472{
1473 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
1474 (int)((level-1) * 128));
1475}
1476
1477
1478/*
1479 * Get last signal level transmitted to or received from the specified port.
1480 */
1481PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
1482 unsigned *tx_level,
1483 unsigned *rx_level)
1484{
1485 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
1486 tx_level, rx_level);
1487}
1488
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001489/*****************************************************************************
1490 * File player.
1491 */
1492
Benny Prijonod5696da2007-07-17 16:25:45 +00001493static char* get_basename(const char *path, unsigned len)
1494{
1495 char *p = ((char*)path) + len;
1496
1497 if (len==0)
1498 return p;
1499
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001500 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00001501
1502 return (p==path) ? p : p+1;
1503}
1504
1505
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001506/*
1507 * Create a file player, and automatically connect this player to
1508 * the conference bridge.
1509 */
1510PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1511 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001512 pjsua_player_id *p_id)
1513{
1514 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001515 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00001516 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001517 pjmedia_port *port;
1518 pj_status_t status;
1519
1520 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1521 return PJ_ETOOMANY;
1522
1523 PJSUA_LOCK();
1524
1525 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1526 if (pjsua_var.player[file_id].port == NULL)
1527 break;
1528 }
1529
1530 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1531 /* This is unexpected */
1532 PJSUA_UNLOCK();
1533 pj_assert(0);
1534 return PJ_EBUG;
1535 }
1536
1537 pj_memcpy(path, filename->ptr, filename->slen);
1538 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00001539
1540 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1541 if (!pool) {
1542 PJSUA_UNLOCK();
1543 return PJ_ENOMEM;
1544 }
1545
1546 status = pjmedia_wav_player_port_create(pool, path,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001547 pjsua_var.mconf_cfg.samples_per_frame *
1548 1000 / pjsua_var.media_cfg.clock_rate,
Benny Prijono00cae612006-07-31 15:19:36 +00001549 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001550 if (status != PJ_SUCCESS) {
1551 PJSUA_UNLOCK();
1552 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001553 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001554 return status;
1555 }
1556
Benny Prijono5297af92008-03-18 13:40:40 +00001557 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001558 port, filename, &slot);
1559 if (status != PJ_SUCCESS) {
1560 pjmedia_port_destroy(port);
1561 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00001562 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1563 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001564 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001565 return status;
1566 }
1567
Benny Prijonoa66c3312007-01-21 23:12:40 +00001568 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00001569 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001570 pjsua_var.player[file_id].port = port;
1571 pjsua_var.player[file_id].slot = slot;
1572
1573 if (p_id) *p_id = file_id;
1574
1575 ++pjsua_var.player_cnt;
1576
1577 PJSUA_UNLOCK();
1578 return PJ_SUCCESS;
1579}
1580
1581
1582/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00001583 * Create a file playlist media port, and automatically add the port
1584 * to the conference bridge.
1585 */
1586PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
1587 unsigned file_count,
1588 const pj_str_t *label,
1589 unsigned options,
1590 pjsua_player_id *p_id)
1591{
1592 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00001593 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001594 pjmedia_port *port;
1595 pj_status_t status;
1596
1597 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1598 return PJ_ETOOMANY;
1599
1600 PJSUA_LOCK();
1601
1602 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1603 if (pjsua_var.player[file_id].port == NULL)
1604 break;
1605 }
1606
1607 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1608 /* This is unexpected */
1609 PJSUA_UNLOCK();
1610 pj_assert(0);
1611 return PJ_EBUG;
1612 }
1613
1614
1615 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
1616 pjsua_var.media_cfg.clock_rate;
1617
Benny Prijonod5696da2007-07-17 16:25:45 +00001618 pool = pjsua_pool_create("playlist", 1000, 1000);
1619 if (!pool) {
1620 PJSUA_UNLOCK();
1621 return PJ_ENOMEM;
1622 }
1623
1624 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001625 file_names, file_count,
1626 ptime, options, 0, &port);
1627 if (status != PJ_SUCCESS) {
1628 PJSUA_UNLOCK();
1629 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001630 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001631 return status;
1632 }
1633
Benny Prijonod5696da2007-07-17 16:25:45 +00001634 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001635 port, &port->info.name, &slot);
1636 if (status != PJ_SUCCESS) {
1637 pjmedia_port_destroy(port);
1638 PJSUA_UNLOCK();
1639 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001640 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001641 return status;
1642 }
1643
1644 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00001645 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001646 pjsua_var.player[file_id].port = port;
1647 pjsua_var.player[file_id].slot = slot;
1648
1649 if (p_id) *p_id = file_id;
1650
1651 ++pjsua_var.player_cnt;
1652
1653 PJSUA_UNLOCK();
1654 return PJ_SUCCESS;
1655
1656}
1657
1658
1659/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001660 * Get conference port ID associated with player.
1661 */
1662PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
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);
1666
1667 return pjsua_var.player[id].slot;
1668}
1669
Benny Prijono469b1522006-12-26 03:05:17 +00001670/*
1671 * Get the media port for the player.
1672 */
Benny Prijonobe41d862008-01-18 13:24:28 +00001673PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00001674 pjmedia_port **p_port)
1675{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001676 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001677 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1678 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1679
1680 *p_port = pjsua_var.player[id].port;
1681
1682 return PJ_SUCCESS;
1683}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001684
1685/*
1686 * Set playback position.
1687 */
1688PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
1689 pj_uint32_t samples)
1690{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001691 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001692 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001693 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001694
1695 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
1696}
1697
1698
1699/*
1700 * Close the file, remove the player from the bridge, and free
1701 * resources associated with the file player.
1702 */
1703PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
1704{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001705 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001706 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1707
1708 PJSUA_LOCK();
1709
1710 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001711 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001712 pjmedia_port_destroy(pjsua_var.player[id].port);
1713 pjsua_var.player[id].port = NULL;
1714 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00001715 pj_pool_release(pjsua_var.player[id].pool);
1716 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001717 pjsua_var.player_cnt--;
1718 }
1719
1720 PJSUA_UNLOCK();
1721
1722 return PJ_SUCCESS;
1723}
1724
1725
1726/*****************************************************************************
1727 * File recorder.
1728 */
1729
1730/*
1731 * Create a file recorder, and automatically connect this recorder to
1732 * the conference bridge.
1733 */
1734PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00001735 unsigned enc_type,
1736 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001737 pj_ssize_t max_size,
1738 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001739 pjsua_recorder_id *p_id)
1740{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001741 enum Format
1742 {
1743 FMT_UNKNOWN,
1744 FMT_WAV,
1745 FMT_MP3,
1746 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001747 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001748 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001749 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00001750 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00001751 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001752 pjmedia_port *port;
1753 pj_status_t status;
1754
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001755 /* Filename must present */
1756 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
1757
Benny Prijono00cae612006-07-31 15:19:36 +00001758 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001759 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001760
Benny Prijono8f310522006-10-20 11:08:49 +00001761 /* Don't support encoding type at present */
1762 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001763
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001764 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
1765 return PJ_ETOOMANY;
1766
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001767 /* Determine the file format */
1768 ext.ptr = filename->ptr + filename->slen - 4;
1769 ext.slen = 4;
1770
1771 if (pj_stricmp2(&ext, ".wav") == 0)
1772 file_format = FMT_WAV;
1773 else if (pj_stricmp2(&ext, ".mp3") == 0)
1774 file_format = FMT_MP3;
1775 else {
1776 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
1777 "determine file format for %.*s",
1778 (int)filename->slen, filename->ptr));
1779 return PJ_ENOTSUP;
1780 }
1781
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001782 PJSUA_LOCK();
1783
1784 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
1785 if (pjsua_var.recorder[file_id].port == NULL)
1786 break;
1787 }
1788
1789 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
1790 /* This is unexpected */
1791 PJSUA_UNLOCK();
1792 pj_assert(0);
1793 return PJ_EBUG;
1794 }
1795
1796 pj_memcpy(path, filename->ptr, filename->slen);
1797 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001798
Benny Prijonod5696da2007-07-17 16:25:45 +00001799 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1800 if (!pool) {
1801 PJSUA_UNLOCK();
1802 return PJ_ENOMEM;
1803 }
1804
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001805 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00001806 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001807 pjsua_var.media_cfg.clock_rate,
1808 pjsua_var.mconf_cfg.channel_count,
1809 pjsua_var.mconf_cfg.samples_per_frame,
1810 pjsua_var.mconf_cfg.bits_per_sample,
1811 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001812 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00001813 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001814 port = NULL;
1815 status = PJ_ENOTSUP;
1816 }
1817
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001818 if (status != PJ_SUCCESS) {
1819 PJSUA_UNLOCK();
1820 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001821 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001822 return status;
1823 }
1824
Benny Prijonod5696da2007-07-17 16:25:45 +00001825 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001826 port, filename, &slot);
1827 if (status != PJ_SUCCESS) {
1828 pjmedia_port_destroy(port);
1829 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00001830 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001831 return status;
1832 }
1833
1834 pjsua_var.recorder[file_id].port = port;
1835 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00001836 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001837
1838 if (p_id) *p_id = file_id;
1839
1840 ++pjsua_var.rec_cnt;
1841
1842 PJSUA_UNLOCK();
1843 return PJ_SUCCESS;
1844}
1845
1846
1847/*
1848 * Get conference port associated with recorder.
1849 */
1850PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(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 return pjsua_var.recorder[id].slot;
1857}
1858
Benny Prijono469b1522006-12-26 03:05:17 +00001859/*
1860 * Get the media port for the recorder.
1861 */
1862PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
1863 pjmedia_port **p_port)
1864{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001865 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1866 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001867 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1868 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1869
1870 *p_port = pjsua_var.recorder[id].port;
1871 return PJ_SUCCESS;
1872}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001873
1874/*
1875 * Destroy recorder (this will complete recording).
1876 */
1877PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
1878{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001879 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1880 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001881 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1882
1883 PJSUA_LOCK();
1884
1885 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001886 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001887 pjmedia_port_destroy(pjsua_var.recorder[id].port);
1888 pjsua_var.recorder[id].port = NULL;
1889 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00001890 pj_pool_release(pjsua_var.recorder[id].pool);
1891 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001892 pjsua_var.rec_cnt--;
1893 }
1894
1895 PJSUA_UNLOCK();
1896
1897 return PJ_SUCCESS;
1898}
1899
1900
1901/*****************************************************************************
1902 * Sound devices.
1903 */
1904
1905/*
1906 * Enum sound devices.
1907 */
1908PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
1909 unsigned *count)
1910{
1911 unsigned i, dev_count;
1912
1913 dev_count = pjmedia_snd_get_dev_count();
1914
1915 if (dev_count > *count) dev_count = *count;
1916
1917 for (i=0; i<dev_count; ++i) {
1918 const pjmedia_snd_dev_info *ci;
1919
1920 ci = pjmedia_snd_get_dev_info(i);
1921 pj_memcpy(&info[i], ci, sizeof(*ci));
1922 }
1923
1924 *count = dev_count;
1925
1926 return PJ_SUCCESS;
1927}
1928
1929
1930/* Close existing sound device */
1931static void close_snd_dev(void)
1932{
1933 /* Close sound device */
1934 if (pjsua_var.snd_port) {
1935 const pjmedia_snd_dev_info *cap_info, *play_info;
1936
1937 cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
1938 play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
1939
1940 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
1941 "%s sound capture device",
1942 play_info->name, cap_info->name));
1943
1944 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
1945 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1946 pjsua_var.snd_port = NULL;
1947 }
1948
1949 /* Close null sound device */
1950 if (pjsua_var.null_snd) {
1951 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
1952 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
1953 pjsua_var.null_snd = NULL;
1954 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001955
1956 if (pjsua_var.snd_pool)
1957 pj_pool_release(pjsua_var.snd_pool);
1958 pjsua_var.snd_pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001959}
1960
1961/*
1962 * Select or change sound device. Application may call this function at
1963 * any time to replace current sound device.
1964 */
1965PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
1966 int playback_dev)
1967{
1968 pjmedia_port *conf_port;
Benny Prijono6dd967c2006-12-26 02:27:14 +00001969 const pjmedia_snd_dev_info *play_info;
Benny Prijonof3758ee2008-02-26 15:32:16 +00001970 unsigned clock_rates[] = {0, 22050, 44100, 48000, 32000, 16000,
1971 8000};
Benny Prijono658a1c52006-10-11 21:56:16 +00001972 unsigned selected_clock_rate = 0;
Benny Prijono26056d82006-10-11 16:03:41 +00001973 unsigned i;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00001974 pjmedia_snd_stream *strm;
1975 pjmedia_snd_stream_info si;
1976 pj_str_t tmp;
Benny Prijono26056d82006-10-11 16:03:41 +00001977 pj_status_t status = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001978
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001979 /* Check if NULL sound device is used */
1980 if (NULL_SND_DEV_ID == capture_dev || NULL_SND_DEV_ID == playback_dev) {
1981 return pjsua_set_null_snd_dev();
1982 }
1983
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001984 /* Close existing sound port */
1985 close_snd_dev();
1986
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001987 /* Create memory pool for sound device. */
1988 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
1989 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001990
Benny Prijono26056d82006-10-11 16:03:41 +00001991 /* Set default clock rate */
Benny Prijonof3758ee2008-02-26 15:32:16 +00001992 clock_rates[0] = pjsua_var.media_cfg.snd_clock_rate;
1993 if (clock_rates[0] == 0)
1994 clock_rates[0] = pjsua_var.media_cfg.clock_rate;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001995
Benny Prijono50f19b32008-03-11 13:15:43 +00001996 /* Get the port0 of the conference bridge. */
1997 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1998 pj_assert(conf_port != NULL);
1999
Benny Prijono26056d82006-10-11 16:03:41 +00002000 /* Attempts to open the sound device with different clock rates */
2001 for (i=0; i<PJ_ARRAY_SIZE(clock_rates); ++i) {
2002 char errmsg[PJ_ERR_MSG_SIZE];
Benny Prijonocf0b4b22007-10-06 17:31:09 +00002003 unsigned fps;
Benny Prijono26056d82006-10-11 16:03:41 +00002004
2005 PJ_LOG(4,(THIS_FILE,
2006 "pjsua_set_snd_dev(): attempting to open devices "
2007 "@%d Hz", clock_rates[i]));
2008
2009 /* Create the sound device. Sound port will start immediately. */
Benny Prijonocf0b4b22007-10-06 17:31:09 +00002010 fps = 1000 / pjsua_var.media_cfg.audio_frame_ptime;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002011 status = pjmedia_snd_port_create(pjsua_var.snd_pool, capture_dev,
Benny Prijono26056d82006-10-11 16:03:41 +00002012 playback_dev,
Benny Prijono7d60d052008-03-29 12:24:20 +00002013 clock_rates[i],
2014 pjsua_var.media_cfg.channel_count,
2015 clock_rates[i]/fps *
2016 pjsua_var.media_cfg.channel_count,
Benny Prijono26056d82006-10-11 16:03:41 +00002017 16, 0, &pjsua_var.snd_port);
2018
Benny Prijono658a1c52006-10-11 21:56:16 +00002019 if (status == PJ_SUCCESS) {
2020 selected_clock_rate = clock_rates[i];
Benny Prijono50f19b32008-03-11 13:15:43 +00002021
2022 /* If there's mismatch between sound port and conference's port,
2023 * create a resample port to bridge them.
2024 */
2025 if (selected_clock_rate != pjsua_var.media_cfg.clock_rate) {
2026 pjmedia_port *resample_port;
2027 unsigned resample_opt = 0;
2028
2029 if (pjsua_var.media_cfg.quality >= 3 &&
2030 pjsua_var.media_cfg.quality <= 4)
2031 {
Nanang Izzuddine85a1832008-05-29 11:03:23 +00002032 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
Benny Prijono50f19b32008-03-11 13:15:43 +00002033 }
2034 else if (pjsua_var.media_cfg.quality < 3) {
Nanang Izzuddine85a1832008-05-29 11:03:23 +00002035 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
Benny Prijono50f19b32008-03-11 13:15:43 +00002036 }
2037
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002038 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
Benny Prijono50f19b32008-03-11 13:15:43 +00002039 conf_port,
2040 selected_clock_rate,
2041 resample_opt,
2042 &resample_port);
2043 if (status != PJ_SUCCESS) {
2044 pj_strerror(status, errmsg, sizeof(errmsg));
2045 PJ_LOG(4, (THIS_FILE,
2046 "Error creating resample port, trying next "
2047 "clock rate",
2048 errmsg));
2049 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2050 pjsua_var.snd_port = NULL;
2051 continue;
2052 } else {
2053 conf_port = resample_port;
2054 break;
2055 }
2056
2057 } else {
2058 break;
2059 }
Benny Prijono658a1c52006-10-11 21:56:16 +00002060 }
Benny Prijono26056d82006-10-11 16:03:41 +00002061
2062 pj_strerror(status, errmsg, sizeof(errmsg));
Benny Prijono658a1c52006-10-11 21:56:16 +00002063 PJ_LOG(4, (THIS_FILE, "..failed: %s", errmsg));
Benny Prijono26056d82006-10-11 16:03:41 +00002064 }
2065
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002066 if (status != PJ_SUCCESS) {
2067 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
2068 return status;
2069 }
2070
Benny Prijonof20687a2006-08-04 18:27:19 +00002071 /* Set AEC */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002072 pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.snd_pool,
Benny Prijono5da50432006-08-07 10:24:52 +00002073 pjsua_var.media_cfg.ec_tail_len,
2074 pjsua_var.media_cfg.ec_options);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002075
Benny Prijono52a93912006-08-04 20:54:37 +00002076 /* Connect sound port to the bridge */
2077 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
2078 conf_port );
2079 if (status != PJ_SUCCESS) {
2080 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
2081 "sound device", status);
2082 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2083 pjsua_var.snd_port = NULL;
2084 return status;
2085 }
2086
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002087 /* Save the device IDs */
2088 pjsua_var.cap_dev = capture_dev;
2089 pjsua_var.play_dev = playback_dev;
2090
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002091 /* Update sound device name. */
2092 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2093 pjmedia_snd_stream_get_info(strm, &si);
2094 play_info = pjmedia_snd_get_dev_info(si.rec_id);
2095
Benny Prijonof3758ee2008-02-26 15:32:16 +00002096 if (si.clock_rate != pjsua_var.media_cfg.clock_rate) {
2097 char tmp_buf[128];
2098 int tmp_buf_len = sizeof(tmp_buf);
2099
2100 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1, "%s (%dKHz)",
2101 play_info->name, si.clock_rate/1000);
2102 pj_strset(&tmp, tmp_buf, tmp_buf_len);
2103 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
2104 } else {
2105 pjmedia_conf_set_port0_name(pjsua_var.mconf,
2106 pj_cstr(&tmp, play_info->name));
2107 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002108
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002109 return PJ_SUCCESS;
2110}
2111
2112
2113/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00002114 * Get currently active sound devices. If sound devices has not been created
2115 * (for example when pjsua_start() is not called), it is possible that
2116 * the function returns PJ_SUCCESS with -1 as device IDs.
2117 */
2118PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
2119 int *playback_dev)
2120{
2121 if (capture_dev) {
2122 *capture_dev = pjsua_var.cap_dev;
2123 }
2124 if (playback_dev) {
2125 *playback_dev = pjsua_var.play_dev;
2126 }
2127
2128 return PJ_SUCCESS;
2129}
2130
2131
2132/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002133 * Use null sound device.
2134 */
2135PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
2136{
2137 pjmedia_port *conf_port;
2138 pj_status_t status;
2139
2140 /* Close existing sound device */
2141 close_snd_dev();
2142
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002143 /* Create memory pool for sound device. */
2144 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2145 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2146
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002147 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
2148
2149 /* Get the port0 of the conference bridge. */
2150 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2151 pj_assert(conf_port != NULL);
2152
2153 /* Create master port, connecting port0 of the conference bridge to
2154 * a null port.
2155 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002156 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002157 conf_port, 0, &pjsua_var.null_snd);
2158 if (status != PJ_SUCCESS) {
2159 pjsua_perror(THIS_FILE, "Unable to create null sound device",
2160 status);
2161 return status;
2162 }
2163
2164 /* Start the master port */
2165 status = pjmedia_master_port_start(pjsua_var.null_snd);
2166 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2167
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002168 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2169 pjsua_var.play_dev = NULL_SND_DEV_ID;
2170
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002171 return PJ_SUCCESS;
2172}
2173
2174
Benny Prijonoe909eac2006-07-27 22:04:56 +00002175
2176/*
2177 * Use no device!
2178 */
2179PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2180{
2181 /* Close existing sound device */
2182 close_snd_dev();
2183
2184 pjsua_var.no_snd = PJ_TRUE;
2185 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2186}
2187
2188
Benny Prijonof20687a2006-08-04 18:27:19 +00002189/*
2190 * Configure the AEC settings of the sound port.
2191 */
Benny Prijono5da50432006-08-07 10:24:52 +00002192PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00002193{
2194 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2195
2196 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00002197 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
2198 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00002199
2200 return PJ_SUCCESS;
2201}
2202
2203
2204/*
2205 * Get current AEC tail length.
2206 */
Benny Prijono22dfe592006-08-06 12:07:13 +00002207PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00002208{
2209 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2210 return PJ_SUCCESS;
2211}
2212
Benny Prijonoe909eac2006-07-27 22:04:56 +00002213
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002214/*****************************************************************************
2215 * Codecs.
2216 */
2217
2218/*
2219 * Enum all supported codecs in the system.
2220 */
2221PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
2222 unsigned *p_count )
2223{
2224 pjmedia_codec_mgr *codec_mgr;
2225 pjmedia_codec_info info[32];
2226 unsigned i, count, prio[32];
2227 pj_status_t status;
2228
2229 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2230 count = PJ_ARRAY_SIZE(info);
2231 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
2232 if (status != PJ_SUCCESS) {
2233 *p_count = 0;
2234 return status;
2235 }
2236
2237 if (count > *p_count) count = *p_count;
2238
2239 for (i=0; i<count; ++i) {
2240 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
2241 id[i].codec_id = pj_str(id[i].buf_);
2242 id[i].priority = (pj_uint8_t) prio[i];
2243 }
2244
2245 *p_count = count;
2246
2247 return PJ_SUCCESS;
2248}
2249
2250
2251/*
2252 * Change codec priority.
2253 */
2254PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
2255 pj_uint8_t priority )
2256{
Benny Prijono88accae2008-06-26 15:48:14 +00002257 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002258 pjmedia_codec_mgr *codec_mgr;
2259
2260 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2261
Benny Prijono88accae2008-06-26 15:48:14 +00002262 if (codec_id->slen==1 && *codec_id->ptr=='*')
2263 codec_id = &all;
2264
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002265 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
2266 priority);
2267}
2268
2269
2270/*
2271 * Get codec parameters.
2272 */
2273PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
2274 pjmedia_codec_param *param )
2275{
Benny Prijono88accae2008-06-26 15:48:14 +00002276 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002277 const pjmedia_codec_info *info;
2278 pjmedia_codec_mgr *codec_mgr;
2279 unsigned count = 1;
2280 pj_status_t status;
2281
2282 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2283
Benny Prijono88accae2008-06-26 15:48:14 +00002284 if (codec_id->slen==1 && *codec_id->ptr=='*')
2285 codec_id = &all;
2286
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002287 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
2288 &count, &info, NULL);
2289 if (status != PJ_SUCCESS)
2290 return status;
2291
2292 if (count != 1)
2293 return PJ_ENOTFOUND;
2294
2295 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
2296 return status;
2297}
2298
2299
2300/*
2301 * Set codec parameters.
2302 */
2303PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
2304 const pjmedia_codec_param *param)
2305{
Benny Prijono00cae612006-07-31 15:19:36 +00002306 PJ_UNUSED_ARG(id);
2307 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002308 PJ_TODO(set_codec_param);
2309 return PJ_SUCCESS;
2310}