blob: 02f32a3f0ad73edc82349ebbc08800f451664882 [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
Benny Prijono32177c02008-06-20 22:44:47 +00003 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjsua-lib/pjsua.h>
20#include <pjsua-lib/pjsua_internal.h>
21
22
23#define THIS_FILE "pjsua_media.c"
24
Nanang Izzuddin68559c32008-06-13 17:01:46 +000025#define DEFAULT_RTP_PORT 4000
26
27#define NULL_SND_DEV_ID -99
Benny Prijono80eee892007-11-03 22:43:23 +000028
29#ifndef PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
30# define PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT 0
31#endif
32
Benny Prijonoeebe9af2006-06-13 22:57:13 +000033
Benny Prijonode479562007-03-15 10:23:55 +000034/* Next RTP port to be used */
35static pj_uint16_t next_rtp_port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000036
37/* Close existing sound device */
38static void close_snd_dev(void);
39
40
Benny Prijonof76e1392008-06-06 14:51:48 +000041static void pjsua_media_config_dup(pj_pool_t *pool,
42 pjsua_media_config *dst,
43 const pjsua_media_config *src)
44{
45 pj_memcpy(dst, src, sizeof(*src));
46 pj_strdup(pool, &dst->turn_server, &src->turn_server);
47 pj_stun_auth_cred_dup(pool, &dst->turn_auth_cred, &src->turn_auth_cred);
48}
49
Benny Prijonoeebe9af2006-06-13 22:57:13 +000050/**
51 * Init media subsystems.
52 */
53pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
54{
Benny Prijonoba5926a2007-05-02 11:29:37 +000055 pj_str_t codec_id = {NULL, 0};
Benny Prijono0498d902006-06-19 14:49:14 +000056 unsigned opt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000057 pj_status_t status;
58
Benny Prijonofc24e692007-01-27 18:31:51 +000059 /* To suppress warning about unused var when all codecs are disabled */
60 PJ_UNUSED_ARG(codec_id);
61
Benny Prijonoeebe9af2006-06-13 22:57:13 +000062 /* Copy configuration */
Benny Prijonof76e1392008-06-06 14:51:48 +000063 pjsua_media_config_dup(pjsua_var.pool, &pjsua_var.media_cfg, cfg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000064
65 /* Normalize configuration */
Benny Prijono50f19b32008-03-11 13:15:43 +000066 if (pjsua_var.media_cfg.snd_clock_rate == 0) {
67 pjsua_var.media_cfg.snd_clock_rate = pjsua_var.media_cfg.clock_rate;
68 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +000069
70 if (pjsua_var.media_cfg.has_ioqueue &&
71 pjsua_var.media_cfg.thread_cnt == 0)
72 {
73 pjsua_var.media_cfg.thread_cnt = 1;
74 }
75
76 if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
77 pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
78 }
79
80 /* Create media endpoint. */
81 status = pjmedia_endpt_create(&pjsua_var.cp.factory,
82 pjsua_var.media_cfg.has_ioqueue? NULL :
83 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
84 pjsua_var.media_cfg.thread_cnt,
85 &pjsua_var.med_endpt);
86 if (status != PJ_SUCCESS) {
87 pjsua_perror(THIS_FILE,
88 "Media stack initialization has returned error",
89 status);
90 return status;
91 }
92
93 /* Register all codecs */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +000094
Benny Prijonoeebe9af2006-06-13 22:57:13 +000095#if PJMEDIA_HAS_SPEEX_CODEC
96 /* Register speex. */
Nanang Izzuddin9dbad152008-06-10 18:56:10 +000097 status = pjmedia_codec_speex_init(pjsua_var.med_endpt,
98 0,
99 pjsua_var.media_cfg.quality,
100 -1);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000101 if (status != PJ_SUCCESS) {
102 pjsua_perror(THIS_FILE, "Error initializing Speex codec",
103 status);
104 return status;
105 }
Benny Prijono7ca96da2006-08-07 12:11:40 +0000106
107 /* Set speex/16000 to higher priority*/
108 codec_id = pj_str("speex/16000");
109 pjmedia_codec_mgr_set_codec_priority(
110 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
111 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
112
113 /* Set speex/8000 to next higher priority*/
114 codec_id = pj_str("speex/8000");
115 pjmedia_codec_mgr_set_codec_priority(
116 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
117 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
118
119
120
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000121#endif /* PJMEDIA_HAS_SPEEX_CODEC */
122
Benny Prijono00cae612006-07-31 15:19:36 +0000123#if PJMEDIA_HAS_ILBC_CODEC
124 /* Register iLBC. */
125 status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt,
126 pjsua_var.media_cfg.ilbc_mode);
127 if (status != PJ_SUCCESS) {
128 pjsua_perror(THIS_FILE, "Error initializing iLBC codec",
129 status);
130 return status;
131 }
132#endif /* PJMEDIA_HAS_ILBC_CODEC */
133
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000134#if PJMEDIA_HAS_GSM_CODEC
135 /* Register GSM */
136 status = pjmedia_codec_gsm_init(pjsua_var.med_endpt);
137 if (status != PJ_SUCCESS) {
138 pjsua_perror(THIS_FILE, "Error initializing GSM codec",
139 status);
140 return status;
141 }
142#endif /* PJMEDIA_HAS_GSM_CODEC */
143
144#if PJMEDIA_HAS_G711_CODEC
145 /* Register PCMA and PCMU */
146 status = pjmedia_codec_g711_init(pjsua_var.med_endpt);
147 if (status != PJ_SUCCESS) {
148 pjsua_perror(THIS_FILE, "Error initializing G711 codec",
149 status);
150 return status;
151 }
152#endif /* PJMEDIA_HAS_G711_CODEC */
153
Benny Prijono7ffd7752008-03-17 14:07:53 +0000154#if PJMEDIA_HAS_G722_CODEC
155 status = pjmedia_codec_g722_init( pjsua_var.med_endpt );
156 if (status != PJ_SUCCESS) {
157 pjsua_perror(THIS_FILE, "Error initializing G722 codec",
158 status);
159 return status;
160 }
161#endif /* PJMEDIA_HAS_G722_CODEC */
162
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000163#if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000164 /* Register IPP codecs */
165 status = pjmedia_codec_ipp_init(pjsua_var.med_endpt);
166 if (status != PJ_SUCCESS) {
167 pjsua_perror(THIS_FILE, "Error initializing IPP codecs",
168 status);
169 return status;
170 }
171
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000172#endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000173
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000174#if PJMEDIA_HAS_L16_CODEC
175 /* Register L16 family codecs, but disable all */
176 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
177 if (status != PJ_SUCCESS) {
178 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
179 status);
180 return status;
181 }
182
183 /* Disable ALL L16 codecs */
184 codec_id = pj_str("L16");
185 pjmedia_codec_mgr_set_codec_priority(
186 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
187 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
188
189#endif /* PJMEDIA_HAS_L16_CODEC */
190
191
192 /* Save additional conference bridge parameters for future
193 * reference.
194 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000195 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000196 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000197 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
198 pjsua_var.mconf_cfg.channel_count *
199 pjsua_var.media_cfg.audio_frame_ptime /
200 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000201
Benny Prijono0498d902006-06-19 14:49:14 +0000202 /* Init options for conference bridge. */
203 opt = PJMEDIA_CONF_NO_DEVICE;
204 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000205 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000206 {
207 opt |= PJMEDIA_CONF_SMALL_FILTER;
208 }
209 else if (pjsua_var.media_cfg.quality < 3) {
210 opt |= PJMEDIA_CONF_USE_LINEAR;
211 }
212
213
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000214 /* Init conference bridge. */
215 status = pjmedia_conf_create(pjsua_var.pool,
216 pjsua_var.media_cfg.max_media_ports,
217 pjsua_var.media_cfg.clock_rate,
218 pjsua_var.mconf_cfg.channel_count,
219 pjsua_var.mconf_cfg.samples_per_frame,
220 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000221 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000222 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000223 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000224 status);
225 return status;
226 }
227
228 /* Create null port just in case user wants to use null sound. */
229 status = pjmedia_null_port_create(pjsua_var.pool,
230 pjsua_var.media_cfg.clock_rate,
231 pjsua_var.mconf_cfg.channel_count,
232 pjsua_var.mconf_cfg.samples_per_frame,
233 pjsua_var.mconf_cfg.bits_per_sample,
234 &pjsua_var.null_port);
235 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
236
Benny Prijono6ba8c542007-10-16 01:34:14 +0000237 /* Perform NAT detection */
238 pjsua_detect_nat_type();
239
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000240 return PJ_SUCCESS;
241}
242
243
244/*
245 * Create RTP and RTCP socket pair, and possibly resolve their public
246 * address via STUN.
247 */
248static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
249 pjmedia_sock_info *skinfo)
250{
251 enum {
252 RTP_RETRY = 100
253 };
254 int i;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000255 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000256 pj_sockaddr_in mapped_addr[2];
257 pj_status_t status = PJ_SUCCESS;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000258 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000259 pj_sock_t sock[2];
260
Benny Prijonoc97608e2007-03-23 16:34:20 +0000261 /* Make sure STUN server resolution has completed */
262 status = pjsua_resolve_stun_server(PJ_TRUE);
263 if (status != PJ_SUCCESS) {
264 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
265 return status;
266 }
267
Benny Prijonode479562007-03-15 10:23:55 +0000268 if (next_rtp_port == 0)
269 next_rtp_port = (pj_uint16_t)cfg->port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000270
271 for (i=0; i<2; ++i)
272 sock[i] = PJ_INVALID_SOCKET;
273
Benny Prijono0a5cad82006-09-26 13:21:02 +0000274 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
275 if (cfg->bound_addr.slen) {
276 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
277 if (status != PJ_SUCCESS) {
278 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
279 status);
280 return status;
281 }
282 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000283
284 /* Loop retry to bind RTP and RTCP sockets. */
Benny Prijonode479562007-03-15 10:23:55 +0000285 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000286
287 /* Create and bind RTP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000288 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000289 if (status != PJ_SUCCESS) {
290 pjsua_perror(THIS_FILE, "socket() error", status);
291 return status;
292 }
293
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000294 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
295 next_rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000296 if (status != PJ_SUCCESS) {
297 pj_sock_close(sock[0]);
298 sock[0] = PJ_INVALID_SOCKET;
299 continue;
300 }
301
302 /* Create and bind RTCP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000303 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000304 if (status != PJ_SUCCESS) {
305 pjsua_perror(THIS_FILE, "socket() error", status);
306 pj_sock_close(sock[0]);
307 return status;
308 }
309
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000310 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
311 (pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000312 if (status != PJ_SUCCESS) {
313 pj_sock_close(sock[0]);
314 sock[0] = PJ_INVALID_SOCKET;
315
316 pj_sock_close(sock[1]);
317 sock[1] = PJ_INVALID_SOCKET;
318 continue;
319 }
320
321 /*
322 * If we're configured to use STUN, then find out the mapped address,
323 * and make sure that the mapped RTCP port is adjacent with the RTP.
324 */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000325 if (pjsua_var.stun_srv.addr.sa_family != 0) {
326 char ip_addr[32];
327 pj_str_t stun_srv;
328
329 pj_ansi_strcpy(ip_addr,
330 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
331 stun_srv = pj_str(ip_addr);
332
Benny Prijono14c2b862007-02-21 00:40:05 +0000333 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000334 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
335 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000336 mapped_addr);
337 if (status != PJ_SUCCESS) {
338 pjsua_perror(THIS_FILE, "STUN resolve error", status);
339 goto on_error;
340 }
341
Benny Prijono80eee892007-11-03 22:43:23 +0000342#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000343 if (pj_ntohs(mapped_addr[1].sin_port) ==
344 pj_ntohs(mapped_addr[0].sin_port)+1)
345 {
346 /* Success! */
347 break;
348 }
349
350 pj_sock_close(sock[0]);
351 sock[0] = PJ_INVALID_SOCKET;
352
353 pj_sock_close(sock[1]);
354 sock[1] = PJ_INVALID_SOCKET;
Benny Prijono80eee892007-11-03 22:43:23 +0000355#else
356 if (pj_ntohs(mapped_addr[1].sin_port) !=
357 pj_ntohs(mapped_addr[0].sin_port)+1)
358 {
359 PJ_LOG(4,(THIS_FILE,
360 "Note: STUN mapped RTCP port %d is not adjacent"
361 " to RTP port %d",
362 pj_ntohs(mapped_addr[1].sin_port),
363 pj_ntohs(mapped_addr[0].sin_port)));
364 }
365 /* Success! */
366 break;
367#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000368
Benny Prijono0a5cad82006-09-26 13:21:02 +0000369 } else if (cfg->public_addr.slen) {
370
371 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000372 (pj_uint16_t)next_rtp_port);
Benny Prijono0a5cad82006-09-26 13:21:02 +0000373 if (status != PJ_SUCCESS)
374 goto on_error;
375
376 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000377 (pj_uint16_t)(next_rtp_port+1));
Benny Prijono0a5cad82006-09-26 13:21:02 +0000378 if (status != PJ_SUCCESS)
379 goto on_error;
380
381 break;
382
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000383 } else {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000384
Benny Prijono42d08d22007-12-20 11:23:07 +0000385 if (bound_addr.sin_addr.s_addr == 0) {
386 pj_sockaddr addr;
387
388 /* Get local IP address. */
389 status = pj_gethostip(pj_AF_INET(), &addr);
390 if (status != PJ_SUCCESS)
391 goto on_error;
392
393 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
394 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000395
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000396 for (i=0; i<2; ++i) {
397 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
Benny Prijono42d08d22007-12-20 11:23:07 +0000398 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000399 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000400
Benny Prijonode479562007-03-15 10:23:55 +0000401 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
402 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000403 break;
404 }
405 }
406
407 if (sock[0] == PJ_INVALID_SOCKET) {
408 PJ_LOG(1,(THIS_FILE,
409 "Unable to find appropriate RTP/RTCP ports combination"));
410 goto on_error;
411 }
412
413
414 skinfo->rtp_sock = sock[0];
415 pj_memcpy(&skinfo->rtp_addr_name,
416 &mapped_addr[0], sizeof(pj_sockaddr_in));
417
418 skinfo->rtcp_sock = sock[1];
419 pj_memcpy(&skinfo->rtcp_addr_name,
420 &mapped_addr[1], sizeof(pj_sockaddr_in));
421
Benny Prijono8b22ce12008-02-08 12:57:55 +0000422 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000423 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
424 sizeof(addr_buf), 3)));
Benny Prijono8b22ce12008-02-08 12:57:55 +0000425 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000426 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
427 sizeof(addr_buf), 3)));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000428
Benny Prijonode479562007-03-15 10:23:55 +0000429 next_rtp_port += 2;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000430 return PJ_SUCCESS;
431
432on_error:
433 for (i=0; i<2; ++i) {
434 if (sock[i] != PJ_INVALID_SOCKET)
435 pj_sock_close(sock[i]);
436 }
437 return status;
438}
439
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000440/* Check if sound device is idle. */
441static void check_snd_dev_idle()
442{
443
444 /* Activate sound device auto-close timer if sound device is idle.
445 * It is idle when there is no port connection in the bridge.
446 */
447 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) &&
448 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
449 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
450 pjsua_var.media_cfg.snd_auto_close_time >= 0)
451 {
452 pj_time_val delay;
453
454 delay.msec = 0;
455 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
456
457 pjsua_var.snd_idle_timer.id = PJ_TRUE;
458 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
459 &delay);
460 }
461}
462
463
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000464/* Timer callback to close sound device */
465static void close_snd_timer_cb( pj_timer_heap_t *th,
466 pj_timer_entry *entry)
467{
468 PJ_UNUSED_ARG(th);
469
470 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
471 pjsua_var.media_cfg.snd_auto_close_time));
472
473 entry->id = PJ_FALSE;
474
475 close_snd_dev();
476}
477
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000478
479/*
480 * Start pjsua media subsystem.
481 */
482pj_status_t pjsua_media_subsys_start(void)
483{
484 pj_status_t status;
485
486 /* Create media for calls, if none is specified */
487 if (pjsua_var.calls[0].med_tp == NULL) {
488 pjsua_transport_config transport_cfg;
489
490 /* Create default transport config */
491 pjsua_transport_config_default(&transport_cfg);
492 transport_cfg.port = DEFAULT_RTP_PORT;
493
494 status = pjsua_media_transports_create(&transport_cfg);
495 if (status != PJ_SUCCESS)
496 return status;
497 }
498
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000499 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
500 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000501
502 return PJ_SUCCESS;
503}
504
505
506/*
507 * Destroy pjsua media subsystem.
508 */
509pj_status_t pjsua_media_subsys_destroy(void)
510{
511 unsigned i;
512
513 close_snd_dev();
514
515 if (pjsua_var.mconf) {
516 pjmedia_conf_destroy(pjsua_var.mconf);
517 pjsua_var.mconf = NULL;
518 }
519
520 if (pjsua_var.null_port) {
521 pjmedia_port_destroy(pjsua_var.null_port);
522 pjsua_var.null_port = NULL;
523 }
524
525 /* Destroy file players */
526 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
527 if (pjsua_var.player[i].port) {
528 pjmedia_port_destroy(pjsua_var.player[i].port);
529 pjsua_var.player[i].port = NULL;
530 }
531 }
532
533 /* Destroy file recorders */
534 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
535 if (pjsua_var.recorder[i].port) {
536 pjmedia_port_destroy(pjsua_var.recorder[i].port);
537 pjsua_var.recorder[i].port = NULL;
538 }
539 }
540
541 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000542 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono311b63f2008-07-14 11:31:40 +0000543 if (pjsua_var.calls[i].med_tp_st != PJSUA_MED_TP_IDLE) {
544 pjsua_media_channel_deinit(i);
545 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000546 if (pjsua_var.calls[i].med_tp) {
547 (*pjsua_var.calls[i].med_tp->op->destroy)(pjsua_var.calls[i].med_tp);
548 pjsua_var.calls[i].med_tp = NULL;
549 }
550 }
551
552 /* Destroy media endpoint. */
553 if (pjsua_var.med_endpt) {
554
555 /* Shutdown all codecs: */
556# if PJMEDIA_HAS_SPEEX_CODEC
557 pjmedia_codec_speex_deinit();
558# endif /* PJMEDIA_HAS_SPEEX_CODEC */
559
560# if PJMEDIA_HAS_GSM_CODEC
561 pjmedia_codec_gsm_deinit();
562# endif /* PJMEDIA_HAS_GSM_CODEC */
563
564# if PJMEDIA_HAS_G711_CODEC
565 pjmedia_codec_g711_deinit();
566# endif /* PJMEDIA_HAS_G711_CODEC */
567
Benny Prijono7ffd7752008-03-17 14:07:53 +0000568# if PJMEDIA_HAS_G722_CODEC
569 pjmedia_codec_g722_deinit();
570# endif /* PJMEDIA_HAS_G722_CODEC */
571
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000572# if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000573 pjmedia_codec_ipp_deinit();
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000574# endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000575
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000576# if PJMEDIA_HAS_L16_CODEC
577 pjmedia_codec_l16_deinit();
578# endif /* PJMEDIA_HAS_L16_CODEC */
579
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000580 pjmedia_endpt_destroy(pjsua_var.med_endpt);
581 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000582
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000583 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000584 // Not necessary, as pjmedia_snd_deinit() should have been called
585 // in pjmedia_endpt_destroy().
586 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000587 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000588
Benny Prijonode479562007-03-15 10:23:55 +0000589 /* Reset RTP port */
590 next_rtp_port = 0;
591
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000592 return PJ_SUCCESS;
593}
594
595
Benny Prijonoc97608e2007-03-23 16:34:20 +0000596/* Create normal UDP media transports */
597static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000598{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000599 unsigned i;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000600 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000601 pj_status_t status;
602
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000603 /* Create each media transport */
604 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
605
Benny Prijono617c5bc2007-04-02 19:51:21 +0000606 status = create_rtp_rtcp_sock(cfg, &skinfo);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000607 if (status != PJ_SUCCESS) {
608 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
609 status);
610 goto on_error;
611 }
Benny Prijonod8179652008-01-23 20:39:07 +0000612
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000613 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000614 &skinfo, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000615 &pjsua_var.calls[i].med_tp);
616 if (status != PJ_SUCCESS) {
617 pjsua_perror(THIS_FILE, "Unable to create media transport",
618 status);
619 goto on_error;
620 }
Benny Prijono00cae612006-07-31 15:19:36 +0000621
Benny Prijonod8179652008-01-23 20:39:07 +0000622 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
623 PJMEDIA_DIR_ENCODING,
624 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000625
Benny Prijonod8179652008-01-23 20:39:07 +0000626 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
627 PJMEDIA_DIR_DECODING,
628 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000629
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000630 }
631
Benny Prijonoc97608e2007-03-23 16:34:20 +0000632 return PJ_SUCCESS;
633
634on_error:
635 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
636 if (pjsua_var.calls[i].med_tp != NULL) {
637 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
638 pjsua_var.calls[i].med_tp = NULL;
639 }
640 }
641
642 return status;
643}
644
645
Benny Prijono096c56c2007-09-15 08:30:16 +0000646/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000647static void on_ice_complete(pjmedia_transport *tp,
648 pj_ice_strans_op op,
649 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000650{
Benny Prijonof76e1392008-06-06 14:51:48 +0000651 unsigned id;
Benny Prijono096c56c2007-09-15 08:30:16 +0000652 pj_bool_t found = PJ_FALSE;
653
Benny Prijono096c56c2007-09-15 08:30:16 +0000654 /* Find call which has this media transport */
655
656 PJSUA_LOCK();
657
Benny Prijonof76e1392008-06-06 14:51:48 +0000658 for (id=0; id<pjsua_var.ua_cfg.max_calls; ++id) {
659 if (pjsua_var.calls[id].med_tp == tp ||
660 pjsua_var.calls[id].med_orig == tp)
661 {
662 found = PJ_TRUE;
663 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000664 }
665 }
666
667 PJSUA_UNLOCK();
668
Benny Prijonof76e1392008-06-06 14:51:48 +0000669 if (!found)
670 return;
671
672 switch (op) {
673 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono224b4e22008-06-19 14:10:28 +0000674 pjsua_var.calls[id].med_tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000675 break;
676 case PJ_ICE_STRANS_OP_NEGOTIATION:
677 if (result != PJ_SUCCESS) {
678 pjsua_var.calls[id].media_st = PJSUA_CALL_MEDIA_ERROR;
679 pjsua_var.calls[id].media_dir = PJMEDIA_DIR_NONE;
680
681 if (pjsua_var.ua_cfg.cb.on_call_media_state) {
682 pjsua_var.ua_cfg.cb.on_call_media_state(id);
683 }
684 }
685 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000686 }
687}
688
689
Benny Prijonof76e1392008-06-06 14:51:48 +0000690/* Parse "HOST:PORT" format */
691static pj_status_t parse_host_port(const pj_str_t *host_port,
692 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000693{
Benny Prijonof76e1392008-06-06 14:51:48 +0000694 pj_str_t str_port;
695
696 str_port.ptr = pj_strchr(host_port, ':');
697 if (str_port.ptr != NULL) {
698 int iport;
699
700 host->ptr = host_port->ptr;
701 host->slen = (str_port.ptr - host->ptr);
702 str_port.ptr++;
703 str_port.slen = host_port->slen - host->slen - 1;
704 iport = (int)pj_strtoul(&str_port);
705 if (iport < 1 || iport > 65535)
706 return PJ_EINVAL;
707 *port = (pj_uint16_t)iport;
708 } else {
709 *host = *host_port;
710 *port = 0;
711 }
712
713 return PJ_SUCCESS;
714}
715
716/* Create ICE media transports (when ice is enabled) */
717static pj_status_t create_ice_media_transports(void)
718{
719 char stunip[PJ_INET6_ADDRSTRLEN];
720 pj_ice_strans_cfg ice_cfg;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000721 unsigned i;
722 pj_status_t status;
723
Benny Prijonoda9785b2007-04-02 20:43:06 +0000724 /* Make sure STUN server resolution has completed */
725 status = pjsua_resolve_stun_server(PJ_TRUE);
726 if (status != PJ_SUCCESS) {
727 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
728 return status;
729 }
730
Benny Prijonof76e1392008-06-06 14:51:48 +0000731 /* Create ICE stream transport configuration */
732 pj_ice_strans_cfg_default(&ice_cfg);
733 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
734 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
735 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
736
737 ice_cfg.af = pj_AF_INET();
738 ice_cfg.resolver = pjsua_var.resolver;
739
740 /* Configure STUN settings */
741 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
742 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
743 ice_cfg.stun.server = pj_str(stunip);
744 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
745 }
746 ice_cfg.stun.no_host_cands = pjsua_var.media_cfg.ice_no_host_cands;
747
748 /* Configure TURN settings */
749 if (pjsua_var.media_cfg.enable_turn) {
750 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
751 &ice_cfg.turn.server,
752 &ice_cfg.turn.port);
753 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
754 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
755 return PJ_EINVAL;
756 }
757 if (ice_cfg.turn.port == 0)
758 ice_cfg.turn.port = 3479;
759 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
760 pj_memcpy(&ice_cfg.turn.auth_cred,
761 &pjsua_var.media_cfg.turn_auth_cred,
762 sizeof(ice_cfg.turn.auth_cred));
763 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000764
Benny Prijonoc97608e2007-03-23 16:34:20 +0000765 /* Create each media transport */
766 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono096c56c2007-09-15 08:30:16 +0000767 pjmedia_ice_cb ice_cb;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000768 char name[32];
Benny Prijonof76e1392008-06-06 14:51:48 +0000769 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000770
Benny Prijono096c56c2007-09-15 08:30:16 +0000771 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
772 ice_cb.on_ice_complete = &on_ice_complete;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000773 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i);
Benny Prijono224b4e22008-06-19 14:10:28 +0000774 pjsua_var.calls[i].med_tp_ready = PJ_EPENDING;
Benny Prijonof76e1392008-06-06 14:51:48 +0000775
776 comp_cnt = 1;
Benny Prijono551af422008-08-07 09:55:52 +0000777 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
Benny Prijonof76e1392008-06-06 14:51:48 +0000778 ++comp_cnt;
779
780 status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
781 &ice_cfg, &ice_cb,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000782 &pjsua_var.calls[i].med_tp);
783 if (status != PJ_SUCCESS) {
784 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
785 status);
786 goto on_error;
787 }
788
Benny Prijonof76e1392008-06-06 14:51:48 +0000789 /* Wait until transport is initialized, or time out */
790 PJSUA_UNLOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000791 while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000792 pjsua_handle_events(100);
793 }
794 PJSUA_LOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000795 if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000796 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
Benny Prijono224b4e22008-06-19 14:10:28 +0000797 pjsua_var.calls[i].med_tp_ready);
798 status = pjsua_var.calls[i].med_tp_ready;
Benny Prijonof76e1392008-06-06 14:51:48 +0000799 goto on_error;
800 }
801
Benny Prijonod8179652008-01-23 20:39:07 +0000802 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
803 PJMEDIA_DIR_ENCODING,
804 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono11da9bc2007-09-15 08:55:00 +0000805
Benny Prijonod8179652008-01-23 20:39:07 +0000806 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
807 PJMEDIA_DIR_DECODING,
808 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000809 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000810
811 return PJ_SUCCESS;
812
813on_error:
814 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
815 if (pjsua_var.calls[i].med_tp != NULL) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000816 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000817 pjsua_var.calls[i].med_tp = NULL;
818 }
819 }
820
Benny Prijonoc97608e2007-03-23 16:34:20 +0000821 return status;
822}
823
824
825/*
826 * Create UDP media transports for all the calls. This function creates
827 * one UDP media transport for each call.
828 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +0000829PJ_DEF(pj_status_t) pjsua_media_transports_create(
830 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000831{
832 pjsua_transport_config cfg;
833 unsigned i;
834 pj_status_t status;
835
836
837 /* Make sure pjsua_init() has been called */
838 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
839
840 PJSUA_LOCK();
841
842 /* Delete existing media transports */
843 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
844 if (pjsua_var.calls[i].med_tp != NULL) {
845 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
846 pjsua_var.calls[i].med_tp = NULL;
847 }
848 }
849
850 /* Copy config */
851 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
852
853 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000854 status = create_ice_media_transports();
Benny Prijonoc97608e2007-03-23 16:34:20 +0000855 } else {
856 status = create_udp_media_transports(&cfg);
857 }
858
859
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000860 PJSUA_UNLOCK();
861
862 return status;
863}
864
865
Benny Prijonoc97608e2007-03-23 16:34:20 +0000866pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
Benny Prijonod8179652008-01-23 20:39:07 +0000867 pjsip_role_e role,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000868 int security_level,
Benny Prijono224b4e22008-06-19 14:10:28 +0000869 pj_pool_t *tmp_pool,
870 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000871 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000872{
873 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono224b4e22008-06-19 14:10:28 +0000874 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000875
Benny Prijonod8179652008-01-23 20:39:07 +0000876#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
877 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
878 pjmedia_srtp_setting srtp_opt;
Benny Prijonoa310bd22008-06-27 21:19:44 +0000879 pjmedia_transport *srtp = NULL;
Benny Prijonod8179652008-01-23 20:39:07 +0000880#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000881
Benny Prijonod8179652008-01-23 20:39:07 +0000882 PJ_UNUSED_ARG(role);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000883
Benny Prijonod8179652008-01-23 20:39:07 +0000884 /* Return error if media transport has not been created yet
885 * (e.g. application is starting)
886 */
887 if (call->med_tp == NULL) {
888 return PJ_EBUSY;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000889 }
890
Benny Prijonod8179652008-01-23 20:39:07 +0000891#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Benny Prijono53a7c702008-04-14 02:57:29 +0000892 /* This function may be called when SRTP transport already exists
893 * (e.g: in re-invite, update), don't need to destroy/re-create.
894 */
895 if (!call->med_orig || call->med_tp == call->med_orig) {
896
897 /* Check if SRTP requires secure signaling */
898 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
899 if (security_level < acc->cfg.srtp_secure_signaling) {
900 if (sip_err_code)
901 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
902 return PJSIP_ESESSIONINSECURE;
903 }
Benny Prijonod8179652008-01-23 20:39:07 +0000904 }
Benny Prijonod8179652008-01-23 20:39:07 +0000905
Benny Prijono53a7c702008-04-14 02:57:29 +0000906 /* Always create SRTP adapter */
907 pjmedia_srtp_setting_default(&srtp_opt);
908 srtp_opt.close_member_tp = PJ_FALSE;
Nanang Izzuddin4375f902008-06-26 19:12:09 +0000909 /* If media session has been ever established, let's use remote's
910 * preference in SRTP usage policy, especially when it is stricter.
911 */
912 if (call->rem_srtp_use > acc->cfg.use_srtp)
913 srtp_opt.use = call->rem_srtp_use;
914 else
915 srtp_opt.use = acc->cfg.use_srtp;
916
Benny Prijono53a7c702008-04-14 02:57:29 +0000917 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
918 call->med_tp,
919 &srtp_opt, &srtp);
920 if (status != PJ_SUCCESS) {
921 if (sip_err_code)
922 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
923 return status;
924 }
Benny Prijonod8179652008-01-23 20:39:07 +0000925
Benny Prijono53a7c702008-04-14 02:57:29 +0000926 /* Set SRTP as current media transport */
927 call->med_orig = call->med_tp;
928 call->med_tp = srtp;
929 }
Benny Prijonod8179652008-01-23 20:39:07 +0000930#else
931 call->med_orig = call->med_tp;
932 PJ_UNUSED_ARG(security_level);
933#endif
934
Benny Prijonoa310bd22008-06-27 21:19:44 +0000935 /* Find out which media line in SDP that we support. If we are offerer,
936 * audio will be at index 0 in SDP.
937 */
938 if (rem_sdp == 0) {
939 call->audio_idx = 0;
940 }
941 /* Otherwise find out the candidate audio media line in SDP */
942 else {
943 unsigned i;
944 pj_bool_t srtp_active;
945
946#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
947 srtp_active = acc->cfg.use_srtp && srtp != NULL;
948#else
949 srtp_active = PJ_FALSE;
950#endif
951
952 /* Media count must have been checked */
953 pj_assert(rem_sdp->media_count != 0);
954
955 for (i=0; i<rem_sdp->media_count; ++i) {
956 const pjmedia_sdp_media *m = rem_sdp->media[i];
957
958 /* Skip if media is not audio */
959 if (pj_stricmp2(&m->desc.media, "audio") != 0)
960 continue;
961
962 /* Skip if media is disabled */
963 if (m->desc.port == 0)
964 continue;
965
966 /* Skip if transport is not supported */
967 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") != 0 &&
968 pj_stricmp2(&m->desc.transport, "RTP/SAVP") != 0)
969 {
970 continue;
971 }
972
973 if (call->audio_idx == -1) {
974 call->audio_idx = i;
975 } else {
976 /* We've found multiple candidates. This could happen
977 * e.g. when remote is offering both RTP/AVP and RTP/AVP,
978 * or when remote for some reason offers two audio.
979 */
980
981 if (srtp_active &&
982 pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0)
983 {
984 /* Prefer RTP/SAVP when our media transport is SRTP */
985 call->audio_idx = i;
986 } else if (!srtp_active &&
987 pj_stricmp2(&m->desc.transport, "RTP/AVP")==0)
988 {
989 /* Prefer RTP/AVP when our media transport is NOT SRTP */
990 call->audio_idx = i;
991 }
992 }
993 }
994 }
995
996 /* Reject offer if we couldn't find a good m=audio line in offer */
997 if (call->audio_idx < 0) {
Benny Prijonoab8dba92008-06-27 21:59:15 +0000998 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +0000999 pjsua_media_channel_deinit(call_id);
Benny Prijonoab8dba92008-06-27 21:59:15 +00001000 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001001 }
1002
1003 PJ_LOG(4,(THIS_FILE, "Media index %d selected for call %d",
1004 call->audio_idx, call->index));
1005
Benny Prijono224b4e22008-06-19 14:10:28 +00001006 /* Create the media transport */
1007 status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001008 rem_sdp, call->audio_idx);
Benny Prijono224b4e22008-06-19 14:10:28 +00001009 if (status != PJ_SUCCESS) {
1010 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1011 pjsua_media_channel_deinit(call_id);
1012 return status;
1013 }
1014
1015 call->med_tp_st = PJSUA_MED_TP_INIT;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001016 return PJ_SUCCESS;
1017}
1018
1019pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1020 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001021 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001022 pjmedia_sdp_session **p_sdp,
1023 int *sip_status_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001024{
Benny Prijonoa310bd22008-06-27 21:19:44 +00001025 enum { MAX_MEDIA = 1 };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001026 pjmedia_sdp_session *sdp;
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001027 pjmedia_transport_info tpinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001028 pjsua_call *call = &pjsua_var.calls[call_id];
1029 pj_status_t status;
1030
Benny Prijono55e82352007-05-10 20:49:08 +00001031 /* Return error if media transport has not been created yet
1032 * (e.g. application is starting)
1033 */
1034 if (call->med_tp == NULL) {
1035 return PJ_EBUSY;
1036 }
1037
Benny Prijonoa310bd22008-06-27 21:19:44 +00001038 /* Media index must have been determined before */
1039 pj_assert(call->audio_idx != -1);
1040
Benny Prijono224b4e22008-06-19 14:10:28 +00001041 /* Create media if it's not created. This could happen when call is
1042 * currently on-hold
1043 */
1044 if (call->med_tp_st == PJSUA_MED_TP_IDLE) {
1045 pjsip_role_e role;
1046 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1047 status = pjsua_media_channel_init(call_id, role, call->secure_level,
1048 pool, rem_sdp, sip_status_code);
1049 if (status != PJ_SUCCESS)
1050 return status;
1051 }
1052
Benny Prijono617c5bc2007-04-02 19:51:21 +00001053 /* Get media socket info */
Benny Prijono734fc2d2008-03-17 16:05:35 +00001054 pjmedia_transport_info_init(&tpinfo);
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001055 pjmedia_transport_get_info(call->med_tp, &tpinfo);
Benny Prijono617c5bc2007-04-02 19:51:21 +00001056
1057 /* Create SDP */
Benny Prijonod8179652008-01-23 20:39:07 +00001058 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA,
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001059 &tpinfo.sock_info, &sdp);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001060 if (status != PJ_SUCCESS) {
1061 if (sip_status_code) *sip_status_code = 500;
Benny Prijono224b4e22008-06-19 14:10:28 +00001062 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001063 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001064
Benny Prijonoa310bd22008-06-27 21:19:44 +00001065 /* If we're answering and the selected media is not the first media
1066 * in SDP, then fill in the unselected media with with zero port.
1067 * Otherwise we'll crash in transport_encode_sdp() because the media
1068 * lines are not aligned between offer and answer.
1069 */
1070 if (rem_sdp && call->audio_idx != 0) {
1071 unsigned i;
1072
1073 for (i=0; i<rem_sdp->media_count; ++i) {
1074 const pjmedia_sdp_media *rem_m = rem_sdp->media[i];
1075 pjmedia_sdp_media *m;
1076 const pjmedia_sdp_attr *a;
1077
1078 if ((int)i == call->audio_idx)
1079 continue;
1080
1081 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1082 pj_strdup(pool, &m->desc.media, &rem_m->desc.media);
1083 pj_strdup(pool, &m->desc.transport, &rem_m->desc.transport);
1084 m->desc.port = 0;
1085
1086 /* Add one format, copy from the offer. And copy the corresponding
1087 * rtpmap and fmtp attributes too.
1088 */
1089 m->desc.fmt_count = 1;
1090 pj_strdup(pool, &m->desc.fmt[0], &rem_m->desc.fmt[0]);
1091 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1092 "rtpmap", &m->desc.fmt[0])) != NULL)
1093 {
1094 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1095 }
1096 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1097 "fmtp", &m->desc.fmt[0])) != NULL)
1098 {
1099 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1100 }
1101
1102 if (i==sdp->media_count)
1103 sdp->media[sdp->media_count++] = m;
1104 else {
1105 pj_array_insert(sdp->media, sizeof(sdp->media[0]),
1106 sdp->media_count, i, &m);
1107 ++sdp->media_count;
1108 }
1109 }
1110 }
1111
Benny Prijono6ba8c542007-10-16 01:34:14 +00001112 /* Add NAT info in the SDP */
1113 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1114 pjmedia_sdp_attr *a;
1115 pj_str_t value;
1116 char nat_info[80];
1117
1118 value.ptr = nat_info;
1119 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1120 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1121 "%d", pjsua_var.nat_type);
1122 } else {
1123 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1124 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1125 "%d %s",
1126 pjsua_var.nat_type,
1127 type_name);
1128 }
1129
1130 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1131
1132 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1133
1134 }
1135
Benny Prijonod8179652008-01-23 20:39:07 +00001136 /* Give the SDP to media transport */
Benny Prijono224b4e22008-06-19 14:10:28 +00001137 status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001138 call->audio_idx);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001139 if (status != PJ_SUCCESS) {
1140 if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono224b4e22008-06-19 14:10:28 +00001141 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001142 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001143
1144 *p_sdp = sdp;
1145 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001146}
1147
1148
1149static void stop_media_session(pjsua_call_id call_id)
1150{
1151 pjsua_call *call = &pjsua_var.calls[call_id];
1152
1153 if (call->conf_slot != PJSUA_INVALID_ID) {
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001154 if (pjsua_var.mconf) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001155 pjsua_conf_remove_port(call->conf_slot);
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001156 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001157 call->conf_slot = PJSUA_INVALID_ID;
1158 }
1159
1160 if (call->session) {
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001161 pjmedia_rtcp_stat stat;
1162
Nanang Izzuddin437d77c2008-08-26 18:04:15 +00001163 if (pjmedia_session_get_stream_stat(call->session, 0, &stat)
1164 == PJ_SUCCESS)
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001165 {
1166 /* Save RTP timestamp & sequence, so when media session is
1167 * restarted, those values will be restored as the initial
1168 * RTP timestamp & sequence of the new media session. So in
1169 * the same call session, RTP timestamp and sequence are
1170 * guaranteed to be contigue.
1171 */
1172 call->rtp_tx_seq_ts_set = 1 | (1 << 1);
1173 call->rtp_tx_seq = stat.rtp_tx_last_seq;
1174 call->rtp_tx_ts = stat.rtp_tx_last_ts;
1175 }
1176
Benny Prijonofc13bf62008-02-20 08:56:15 +00001177 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1178 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0);
1179 }
1180
Benny Prijonoc97608e2007-03-23 16:34:20 +00001181 pjmedia_session_destroy(call->session);
1182 call->session = NULL;
1183
1184 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
1185 call_id));
1186
1187 }
1188
1189 call->media_st = PJSUA_CALL_MEDIA_NONE;
1190}
1191
1192pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1193{
1194 pjsua_call *call = &pjsua_var.calls[call_id];
1195
1196 stop_media_session(call_id);
1197
Benny Prijono224b4e22008-06-19 14:10:28 +00001198 if (call->med_tp_st != PJSUA_MED_TP_IDLE) {
1199 pjmedia_transport_media_stop(call->med_tp);
1200 call->med_tp_st = PJSUA_MED_TP_IDLE;
1201 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001202
Benny Prijono311b63f2008-07-14 11:31:40 +00001203 if (call->med_orig && call->med_tp && call->med_tp != call->med_orig) {
Benny Prijonod8179652008-01-23 20:39:07 +00001204 pjmedia_transport_close(call->med_tp);
1205 call->med_tp = call->med_orig;
1206 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001207 return PJ_SUCCESS;
1208}
1209
1210
1211/*
1212 * DTMF callback from the stream.
1213 */
1214static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1215 int digit)
1216{
1217 PJ_UNUSED_ARG(strm);
1218
Benny Prijono0c068262008-02-14 14:38:52 +00001219 /* For discussions about call mutex protection related to this
1220 * callback, please see ticket #460:
1221 * http://trac.pjsip.org/repos/ticket/460#comment:4
1222 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001223 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1224 pjsua_call_id call_id;
1225
Benny Prijonod8179652008-01-23 20:39:07 +00001226 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001227 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1228 }
1229}
1230
1231
1232pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijono224b4e22008-06-19 14:10:28 +00001233 const pjmedia_sdp_session *local_sdp,
Benny Prijonodbce2cf2007-03-28 16:24:00 +00001234 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001235{
1236 int prev_media_st = 0;
1237 pjsua_call *call = &pjsua_var.calls[call_id];
1238 pjmedia_session_info sess_info;
Benny Prijono91e567e2007-12-28 08:51:58 +00001239 pjmedia_stream_info *si = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001240 pjmedia_port *media_port;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001241 pj_status_t status;
1242
1243 /* Destroy existing media session, if any. */
1244 prev_media_st = call->media_st;
1245 stop_media_session(call->index);
1246
1247 /* Create media session info based on SDP parameters.
Benny Prijonoc97608e2007-03-23 16:34:20 +00001248 */
1249 status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
1250 pjsua_var.med_endpt,
Benny Prijono91e567e2007-12-28 08:51:58 +00001251 PJMEDIA_MAX_SDP_MEDIA, &sess_info,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001252 local_sdp, remote_sdp);
1253 if (status != PJ_SUCCESS)
1254 return status;
1255
Benny Prijonoa310bd22008-06-27 21:19:44 +00001256 /* Find which session is audio */
1257 PJ_ASSERT_RETURN(call->audio_idx != -1, PJ_EBUG);
1258 PJ_ASSERT_RETURN(call->audio_idx < (int)sess_info.stream_cnt, PJ_EBUG);
1259 si = &sess_info.stream_info[call->audio_idx];
Benny Prijono91e567e2007-12-28 08:51:58 +00001260
1261 /* Reset session info with only one media stream */
1262 sess_info.stream_cnt = 1;
1263 if (si != &sess_info.stream_info[0])
1264 pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001265
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001266 /* Check if no media is active */
Benny Prijono91e567e2007-12-28 08:51:58 +00001267 if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001268 {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001269 /* Call media state */
1270 call->media_st = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001271
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001272 /* Call media direction */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001273 call->media_dir = PJMEDIA_DIR_NONE;
1274
Benny Prijonod8179652008-01-23 20:39:07 +00001275 /* Shutdown transport's session */
1276 pjmedia_transport_media_stop(call->med_tp);
Benny Prijono224b4e22008-06-19 14:10:28 +00001277 call->med_tp_st = PJSUA_MED_TP_IDLE;
Benny Prijono667952e2007-04-02 19:27:54 +00001278
Benny Prijonoc97608e2007-03-23 16:34:20 +00001279 /* No need because we need keepalive? */
1280
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001281 /* Close upper entry of transport stack */
1282 if (call->med_orig && (call->med_tp != call->med_orig)) {
1283 pjmedia_transport_close(call->med_tp);
1284 call->med_tp = call->med_orig;
1285 }
1286
Benny Prijonoc97608e2007-03-23 16:34:20 +00001287 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001288 pjmedia_transport_info tp_info;
1289
Benny Prijono224b4e22008-06-19 14:10:28 +00001290 /* Start/restart media transport */
Benny Prijonod8179652008-01-23 20:39:07 +00001291 status = pjmedia_transport_media_start(call->med_tp,
1292 call->inv->pool,
1293 local_sdp, remote_sdp, 0);
1294 if (status != PJ_SUCCESS)
1295 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001296
Benny Prijono224b4e22008-06-19 14:10:28 +00001297 call->med_tp_st = PJSUA_MED_TP_RUNNING;
1298
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001299 /* Get remote SRTP usage policy */
1300 pjmedia_transport_info_init(&tp_info);
1301 pjmedia_transport_get_info(call->med_tp, &tp_info);
1302 if (tp_info.specific_info_cnt > 0) {
1303 int i;
1304 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1305 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1306 {
1307 pjmedia_srtp_info *srtp_info =
1308 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1309
1310 call->rem_srtp_use = srtp_info->peer_use;
1311 break;
1312 }
1313 }
1314 }
1315
Benny Prijonoc97608e2007-03-23 16:34:20 +00001316 /* Override ptime, if this option is specified. */
1317 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001318 si->param->setting.frm_per_pkt = (pj_uint8_t)
1319 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1320 if (si->param->setting.frm_per_pkt == 0)
1321 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001322 }
1323
1324 /* Disable VAD, if this option is specified. */
1325 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001326 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001327 }
1328
1329
1330 /* Optionally, application may modify other stream settings here
1331 * (such as jitter buffer parameters, codec ptime, etc.)
1332 */
Benny Prijono91e567e2007-12-28 08:51:58 +00001333 si->jb_init = pjsua_var.media_cfg.jb_init;
1334 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1335 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1336 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001337
Benny Prijono8147f402007-11-21 14:50:07 +00001338 /* Set SSRC */
Benny Prijono91e567e2007-12-28 08:51:58 +00001339 si->ssrc = call->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00001340
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001341 /* Set RTP timestamp & sequence, normally these value are intialized
1342 * automatically when stream session created, but for some cases (e.g:
1343 * call reinvite, call update) timestamp and sequence need to be kept
1344 * contigue.
1345 */
1346 si->rtp_ts = call->rtp_tx_ts;
1347 si->rtp_seq = call->rtp_tx_seq;
1348 si->rtp_seq_ts_set = call->rtp_tx_seq_ts_set;
1349
Benny Prijonoc97608e2007-03-23 16:34:20 +00001350 /* Create session based on session info. */
1351 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
1352 &call->med_tp,
1353 call, &call->session );
1354 if (status != PJ_SUCCESS) {
1355 return status;
1356 }
1357
1358 /* If DTMF callback is installed by application, install our
1359 * callback to the session.
1360 */
1361 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1362 pjmedia_session_set_dtmf_callback(call->session, 0,
1363 &dtmf_callback,
Benny Prijonod8179652008-01-23 20:39:07 +00001364 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001365 }
1366
1367 /* Get the port interface of the first stream in the session.
1368 * We need the port interface to add to the conference bridge.
1369 */
1370 pjmedia_session_get_port(call->session, 0, &media_port);
1371
Benny Prijonofc13bf62008-02-20 08:56:15 +00001372 /* Notify application about stream creation.
1373 * Note: application may modify media_port to point to different
1374 * media port
1375 */
1376 if (pjsua_var.ua_cfg.cb.on_stream_created) {
1377 pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session,
1378 0, &media_port);
1379 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001380
1381 /*
1382 * Add the call to conference bridge.
1383 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001384 {
1385 char tmp[PJSIP_MAX_URL_SIZE];
1386 pj_str_t port_name;
1387
1388 port_name.ptr = tmp;
1389 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1390 call->inv->dlg->remote.info->uri,
1391 tmp, sizeof(tmp));
1392 if (port_name.slen < 1) {
1393 port_name = pj_str("call");
1394 }
1395 status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
1396 media_port,
1397 &port_name,
1398 (unsigned*)&call->conf_slot);
1399 if (status != PJ_SUCCESS) {
1400 return status;
1401 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001402 }
1403
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001404 /* Call media direction */
Benny Prijono91e567e2007-12-28 08:51:58 +00001405 call->media_dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001406
1407 /* Call media state */
1408 if (call->local_hold)
1409 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
1410 else if (call->media_dir == PJMEDIA_DIR_DECODING)
1411 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
1412 else
1413 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001414 }
1415
1416 /* Print info. */
1417 {
1418 char info[80];
1419 int info_len = 0;
1420 unsigned i;
1421
1422 for (i=0; i<sess_info.stream_cnt; ++i) {
1423 int len;
1424 const char *dir;
1425 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1426
1427 switch (strm_info->dir) {
1428 case PJMEDIA_DIR_NONE:
1429 dir = "inactive";
1430 break;
1431 case PJMEDIA_DIR_ENCODING:
1432 dir = "sendonly";
1433 break;
1434 case PJMEDIA_DIR_DECODING:
1435 dir = "recvonly";
1436 break;
1437 case PJMEDIA_DIR_ENCODING_DECODING:
1438 dir = "sendrecv";
1439 break;
1440 default:
1441 dir = "unknown";
1442 break;
1443 }
1444 len = pj_ansi_sprintf( info+info_len,
1445 ", stream #%d: %.*s (%s)", i,
1446 (int)strm_info->fmt.encoding_name.slen,
1447 strm_info->fmt.encoding_name.ptr,
1448 dir);
1449 if (len > 0)
1450 info_len += len;
1451 }
1452 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1453 }
1454
1455 return PJ_SUCCESS;
1456}
1457
1458
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001459/*
1460 * Get maxinum number of conference ports.
1461 */
1462PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1463{
1464 return pjsua_var.media_cfg.max_media_ports;
1465}
1466
1467
1468/*
1469 * Get current number of active ports in the bridge.
1470 */
1471PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1472{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001473 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001474 unsigned count = PJ_ARRAY_SIZE(ports);
1475 pj_status_t status;
1476
1477 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1478 if (status != PJ_SUCCESS)
1479 count = 0;
1480
1481 return count;
1482}
1483
1484
1485/*
1486 * Enumerate all conference ports.
1487 */
1488PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1489 unsigned *count)
1490{
1491 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1492}
1493
1494
1495/*
1496 * Get information about the specified conference port
1497 */
1498PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1499 pjsua_conf_port_info *info)
1500{
1501 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001502 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001503 pj_status_t status;
1504
1505 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1506 if (status != PJ_SUCCESS)
1507 return status;
1508
Benny Prijonoac623b32006-07-03 15:19:31 +00001509 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001510 info->slot_id = id;
1511 info->name = cinfo.name;
1512 info->clock_rate = cinfo.clock_rate;
1513 info->channel_count = cinfo.channel_count;
1514 info->samples_per_frame = cinfo.samples_per_frame;
1515 info->bits_per_sample = cinfo.bits_per_sample;
1516
1517 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001518 info->listener_cnt = cinfo.listener_cnt;
1519 for (i=0; i<cinfo.listener_cnt; ++i) {
1520 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001521 }
1522
1523 return PJ_SUCCESS;
1524}
1525
1526
1527/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001528 * Add arbitrary media port to PJSUA's conference bridge.
1529 */
1530PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1531 pjmedia_port *port,
1532 pjsua_conf_port_id *p_id)
1533{
1534 pj_status_t status;
1535
1536 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1537 port, NULL, (unsigned*)p_id);
1538 if (status != PJ_SUCCESS) {
1539 if (p_id)
1540 *p_id = PJSUA_INVALID_ID;
1541 }
1542
1543 return status;
1544}
1545
1546
1547/*
1548 * Remove arbitrary slot from the conference bridge.
1549 */
1550PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1551{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001552 pj_status_t status;
1553
1554 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1555 check_snd_dev_idle();
1556
1557 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00001558}
1559
1560
1561/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001562 * Establish unidirectional media flow from souce to sink.
1563 */
1564PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1565 pjsua_conf_port_id sink)
1566{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001567 /* If sound device idle timer is active, cancel it first. */
1568 if (pjsua_var.snd_idle_timer.id) {
1569 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
1570 pjsua_var.snd_idle_timer.id = PJ_FALSE;
1571 }
1572
1573 /* Create sound port if none is instantiated */
1574 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1575 !pjsua_var.no_snd)
1576 {
1577 pj_status_t status;
1578
1579 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1580 if (status != PJ_SUCCESS) {
1581 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1582 return status;
1583 }
1584 }
1585
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001586 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
1587}
1588
1589
1590/*
1591 * Disconnect media flow from the source to destination port.
1592 */
1593PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
1594 pjsua_conf_port_id sink)
1595{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001596 pj_status_t status;
1597
1598 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001599 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001600
1601 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001602}
1603
1604
Benny Prijono6dd967c2006-12-26 02:27:14 +00001605/*
1606 * Adjust the signal level to be transmitted from the bridge to the
1607 * specified port by making it louder or quieter.
1608 */
1609PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
1610 float level)
1611{
1612 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
1613 (int)((level-1) * 128));
1614}
1615
1616/*
1617 * Adjust the signal level to be received from the specified port (to
1618 * the bridge) by making it louder or quieter.
1619 */
1620PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
1621 float level)
1622{
1623 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
1624 (int)((level-1) * 128));
1625}
1626
1627
1628/*
1629 * Get last signal level transmitted to or received from the specified port.
1630 */
1631PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
1632 unsigned *tx_level,
1633 unsigned *rx_level)
1634{
1635 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
1636 tx_level, rx_level);
1637}
1638
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001639/*****************************************************************************
1640 * File player.
1641 */
1642
Benny Prijonod5696da2007-07-17 16:25:45 +00001643static char* get_basename(const char *path, unsigned len)
1644{
1645 char *p = ((char*)path) + len;
1646
1647 if (len==0)
1648 return p;
1649
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001650 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00001651
1652 return (p==path) ? p : p+1;
1653}
1654
1655
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001656/*
1657 * Create a file player, and automatically connect this player to
1658 * the conference bridge.
1659 */
1660PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1661 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001662 pjsua_player_id *p_id)
1663{
1664 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001665 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00001666 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001667 pjmedia_port *port;
1668 pj_status_t status;
1669
1670 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1671 return PJ_ETOOMANY;
1672
1673 PJSUA_LOCK();
1674
1675 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1676 if (pjsua_var.player[file_id].port == NULL)
1677 break;
1678 }
1679
1680 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1681 /* This is unexpected */
1682 PJSUA_UNLOCK();
1683 pj_assert(0);
1684 return PJ_EBUG;
1685 }
1686
1687 pj_memcpy(path, filename->ptr, filename->slen);
1688 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00001689
1690 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1691 if (!pool) {
1692 PJSUA_UNLOCK();
1693 return PJ_ENOMEM;
1694 }
1695
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00001696 status = pjmedia_wav_player_port_create(
1697 pool, path,
1698 pjsua_var.mconf_cfg.samples_per_frame *
1699 1000 / pjsua_var.media_cfg.channel_count /
1700 pjsua_var.media_cfg.clock_rate,
1701 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001702 if (status != PJ_SUCCESS) {
1703 PJSUA_UNLOCK();
1704 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001705 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001706 return status;
1707 }
1708
Benny Prijono5297af92008-03-18 13:40:40 +00001709 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001710 port, filename, &slot);
1711 if (status != PJ_SUCCESS) {
1712 pjmedia_port_destroy(port);
1713 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00001714 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1715 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001716 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001717 return status;
1718 }
1719
Benny Prijonoa66c3312007-01-21 23:12:40 +00001720 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00001721 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001722 pjsua_var.player[file_id].port = port;
1723 pjsua_var.player[file_id].slot = slot;
1724
1725 if (p_id) *p_id = file_id;
1726
1727 ++pjsua_var.player_cnt;
1728
1729 PJSUA_UNLOCK();
1730 return PJ_SUCCESS;
1731}
1732
1733
1734/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00001735 * Create a file playlist media port, and automatically add the port
1736 * to the conference bridge.
1737 */
1738PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
1739 unsigned file_count,
1740 const pj_str_t *label,
1741 unsigned options,
1742 pjsua_player_id *p_id)
1743{
1744 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00001745 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001746 pjmedia_port *port;
1747 pj_status_t status;
1748
1749 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1750 return PJ_ETOOMANY;
1751
1752 PJSUA_LOCK();
1753
1754 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1755 if (pjsua_var.player[file_id].port == NULL)
1756 break;
1757 }
1758
1759 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1760 /* This is unexpected */
1761 PJSUA_UNLOCK();
1762 pj_assert(0);
1763 return PJ_EBUG;
1764 }
1765
1766
1767 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
1768 pjsua_var.media_cfg.clock_rate;
1769
Benny Prijonod5696da2007-07-17 16:25:45 +00001770 pool = pjsua_pool_create("playlist", 1000, 1000);
1771 if (!pool) {
1772 PJSUA_UNLOCK();
1773 return PJ_ENOMEM;
1774 }
1775
1776 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001777 file_names, file_count,
1778 ptime, options, 0, &port);
1779 if (status != PJ_SUCCESS) {
1780 PJSUA_UNLOCK();
1781 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001782 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001783 return status;
1784 }
1785
Benny Prijonod5696da2007-07-17 16:25:45 +00001786 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001787 port, &port->info.name, &slot);
1788 if (status != PJ_SUCCESS) {
1789 pjmedia_port_destroy(port);
1790 PJSUA_UNLOCK();
1791 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001792 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001793 return status;
1794 }
1795
1796 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00001797 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001798 pjsua_var.player[file_id].port = port;
1799 pjsua_var.player[file_id].slot = slot;
1800
1801 if (p_id) *p_id = file_id;
1802
1803 ++pjsua_var.player_cnt;
1804
1805 PJSUA_UNLOCK();
1806 return PJ_SUCCESS;
1807
1808}
1809
1810
1811/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001812 * Get conference port ID associated with player.
1813 */
1814PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
1815{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001816 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001817 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1818
1819 return pjsua_var.player[id].slot;
1820}
1821
Benny Prijono469b1522006-12-26 03:05:17 +00001822/*
1823 * Get the media port for the player.
1824 */
Benny Prijonobe41d862008-01-18 13:24:28 +00001825PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00001826 pjmedia_port **p_port)
1827{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001828 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001829 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1830 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1831
1832 *p_port = pjsua_var.player[id].port;
1833
1834 return PJ_SUCCESS;
1835}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001836
1837/*
1838 * Set playback position.
1839 */
1840PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
1841 pj_uint32_t samples)
1842{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001843 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001844 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001845 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001846
1847 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
1848}
1849
1850
1851/*
1852 * Close the file, remove the player from the bridge, and free
1853 * resources associated with the file player.
1854 */
1855PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
1856{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001857 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001858 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1859
1860 PJSUA_LOCK();
1861
1862 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001863 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001864 pjmedia_port_destroy(pjsua_var.player[id].port);
1865 pjsua_var.player[id].port = NULL;
1866 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00001867 pj_pool_release(pjsua_var.player[id].pool);
1868 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001869 pjsua_var.player_cnt--;
1870 }
1871
1872 PJSUA_UNLOCK();
1873
1874 return PJ_SUCCESS;
1875}
1876
1877
1878/*****************************************************************************
1879 * File recorder.
1880 */
1881
1882/*
1883 * Create a file recorder, and automatically connect this recorder to
1884 * the conference bridge.
1885 */
1886PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00001887 unsigned enc_type,
1888 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001889 pj_ssize_t max_size,
1890 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001891 pjsua_recorder_id *p_id)
1892{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001893 enum Format
1894 {
1895 FMT_UNKNOWN,
1896 FMT_WAV,
1897 FMT_MP3,
1898 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001899 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001900 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001901 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00001902 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00001903 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001904 pjmedia_port *port;
1905 pj_status_t status;
1906
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001907 /* Filename must present */
1908 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
1909
Benny Prijono00cae612006-07-31 15:19:36 +00001910 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001911 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001912
Benny Prijono8f310522006-10-20 11:08:49 +00001913 /* Don't support encoding type at present */
1914 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001915
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001916 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
1917 return PJ_ETOOMANY;
1918
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001919 /* Determine the file format */
1920 ext.ptr = filename->ptr + filename->slen - 4;
1921 ext.slen = 4;
1922
1923 if (pj_stricmp2(&ext, ".wav") == 0)
1924 file_format = FMT_WAV;
1925 else if (pj_stricmp2(&ext, ".mp3") == 0)
1926 file_format = FMT_MP3;
1927 else {
1928 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
1929 "determine file format for %.*s",
1930 (int)filename->slen, filename->ptr));
1931 return PJ_ENOTSUP;
1932 }
1933
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001934 PJSUA_LOCK();
1935
1936 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
1937 if (pjsua_var.recorder[file_id].port == NULL)
1938 break;
1939 }
1940
1941 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
1942 /* This is unexpected */
1943 PJSUA_UNLOCK();
1944 pj_assert(0);
1945 return PJ_EBUG;
1946 }
1947
1948 pj_memcpy(path, filename->ptr, filename->slen);
1949 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001950
Benny Prijonod5696da2007-07-17 16:25:45 +00001951 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1952 if (!pool) {
1953 PJSUA_UNLOCK();
1954 return PJ_ENOMEM;
1955 }
1956
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001957 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00001958 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001959 pjsua_var.media_cfg.clock_rate,
1960 pjsua_var.mconf_cfg.channel_count,
1961 pjsua_var.mconf_cfg.samples_per_frame,
1962 pjsua_var.mconf_cfg.bits_per_sample,
1963 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001964 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00001965 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001966 port = NULL;
1967 status = PJ_ENOTSUP;
1968 }
1969
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001970 if (status != PJ_SUCCESS) {
1971 PJSUA_UNLOCK();
1972 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001973 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001974 return status;
1975 }
1976
Benny Prijonod5696da2007-07-17 16:25:45 +00001977 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001978 port, filename, &slot);
1979 if (status != PJ_SUCCESS) {
1980 pjmedia_port_destroy(port);
1981 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00001982 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001983 return status;
1984 }
1985
1986 pjsua_var.recorder[file_id].port = port;
1987 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00001988 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001989
1990 if (p_id) *p_id = file_id;
1991
1992 ++pjsua_var.rec_cnt;
1993
1994 PJSUA_UNLOCK();
1995 return PJ_SUCCESS;
1996}
1997
1998
1999/*
2000 * Get conference port associated with recorder.
2001 */
2002PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
2003{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002004 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2005 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002006 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2007
2008 return pjsua_var.recorder[id].slot;
2009}
2010
Benny Prijono469b1522006-12-26 03:05:17 +00002011/*
2012 * Get the media port for the recorder.
2013 */
2014PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
2015 pjmedia_port **p_port)
2016{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002017 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2018 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002019 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2020 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2021
2022 *p_port = pjsua_var.recorder[id].port;
2023 return PJ_SUCCESS;
2024}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002025
2026/*
2027 * Destroy recorder (this will complete recording).
2028 */
2029PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
2030{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002031 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2032 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002033 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2034
2035 PJSUA_LOCK();
2036
2037 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002038 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002039 pjmedia_port_destroy(pjsua_var.recorder[id].port);
2040 pjsua_var.recorder[id].port = NULL;
2041 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002042 pj_pool_release(pjsua_var.recorder[id].pool);
2043 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002044 pjsua_var.rec_cnt--;
2045 }
2046
2047 PJSUA_UNLOCK();
2048
2049 return PJ_SUCCESS;
2050}
2051
2052
2053/*****************************************************************************
2054 * Sound devices.
2055 */
2056
2057/*
2058 * Enum sound devices.
2059 */
2060PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
2061 unsigned *count)
2062{
2063 unsigned i, dev_count;
2064
2065 dev_count = pjmedia_snd_get_dev_count();
2066
2067 if (dev_count > *count) dev_count = *count;
2068
2069 for (i=0; i<dev_count; ++i) {
2070 const pjmedia_snd_dev_info *ci;
2071
2072 ci = pjmedia_snd_get_dev_info(i);
2073 pj_memcpy(&info[i], ci, sizeof(*ci));
2074 }
2075
2076 *count = dev_count;
2077
2078 return PJ_SUCCESS;
2079}
2080
2081
2082/* Close existing sound device */
2083static void close_snd_dev(void)
2084{
2085 /* Close sound device */
2086 if (pjsua_var.snd_port) {
2087 const pjmedia_snd_dev_info *cap_info, *play_info;
2088
2089 cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
2090 play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
2091
2092 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
2093 "%s sound capture device",
2094 play_info->name, cap_info->name));
2095
2096 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
2097 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2098 pjsua_var.snd_port = NULL;
2099 }
2100
2101 /* Close null sound device */
2102 if (pjsua_var.null_snd) {
2103 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
2104 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
2105 pjsua_var.null_snd = NULL;
2106 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002107
2108 if (pjsua_var.snd_pool)
2109 pj_pool_release(pjsua_var.snd_pool);
2110 pjsua_var.snd_pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002111}
2112
2113/*
2114 * Select or change sound device. Application may call this function at
2115 * any time to replace current sound device.
2116 */
2117PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
2118 int playback_dev)
2119{
2120 pjmedia_port *conf_port;
Benny Prijono6dd967c2006-12-26 02:27:14 +00002121 const pjmedia_snd_dev_info *play_info;
Benny Prijonof3758ee2008-02-26 15:32:16 +00002122 unsigned clock_rates[] = {0, 22050, 44100, 48000, 32000, 16000,
2123 8000};
Benny Prijono658a1c52006-10-11 21:56:16 +00002124 unsigned selected_clock_rate = 0;
Benny Prijono26056d82006-10-11 16:03:41 +00002125 unsigned i;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002126 pjmedia_snd_stream *strm;
2127 pjmedia_snd_stream_info si;
2128 pj_str_t tmp;
Benny Prijono26056d82006-10-11 16:03:41 +00002129 pj_status_t status = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002130
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002131 /* Check if NULL sound device is used */
2132 if (NULL_SND_DEV_ID == capture_dev || NULL_SND_DEV_ID == playback_dev) {
2133 return pjsua_set_null_snd_dev();
2134 }
2135
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002136 /* Close existing sound port */
2137 close_snd_dev();
2138
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002139 /* Create memory pool for sound device. */
2140 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2141 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002142
Benny Prijono26056d82006-10-11 16:03:41 +00002143 /* Set default clock rate */
Benny Prijonof3758ee2008-02-26 15:32:16 +00002144 clock_rates[0] = pjsua_var.media_cfg.snd_clock_rate;
2145 if (clock_rates[0] == 0)
2146 clock_rates[0] = pjsua_var.media_cfg.clock_rate;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002147
Benny Prijono50f19b32008-03-11 13:15:43 +00002148 /* Get the port0 of the conference bridge. */
2149 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2150 pj_assert(conf_port != NULL);
2151
Benny Prijono26056d82006-10-11 16:03:41 +00002152 /* Attempts to open the sound device with different clock rates */
2153 for (i=0; i<PJ_ARRAY_SIZE(clock_rates); ++i) {
2154 char errmsg[PJ_ERR_MSG_SIZE];
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002155 unsigned samples_per_frame;
Benny Prijono26056d82006-10-11 16:03:41 +00002156
2157 PJ_LOG(4,(THIS_FILE,
2158 "pjsua_set_snd_dev(): attempting to open devices "
2159 "@%d Hz", clock_rates[i]));
2160
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002161 samples_per_frame = clock_rates[i] *
2162 pjsua_var.media_cfg.audio_frame_ptime *
2163 pjsua_var.media_cfg.channel_count / 1000;
2164
Benny Prijono26056d82006-10-11 16:03:41 +00002165 /* Create the sound device. Sound port will start immediately. */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002166 status = pjmedia_snd_port_create(pjsua_var.snd_pool, capture_dev,
Benny Prijono26056d82006-10-11 16:03:41 +00002167 playback_dev,
Benny Prijono7d60d052008-03-29 12:24:20 +00002168 clock_rates[i],
2169 pjsua_var.media_cfg.channel_count,
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002170 samples_per_frame,
Benny Prijono26056d82006-10-11 16:03:41 +00002171 16, 0, &pjsua_var.snd_port);
2172
Benny Prijono658a1c52006-10-11 21:56:16 +00002173 if (status == PJ_SUCCESS) {
2174 selected_clock_rate = clock_rates[i];
Benny Prijono50f19b32008-03-11 13:15:43 +00002175
2176 /* If there's mismatch between sound port and conference's port,
2177 * create a resample port to bridge them.
2178 */
2179 if (selected_clock_rate != pjsua_var.media_cfg.clock_rate) {
2180 pjmedia_port *resample_port;
2181 unsigned resample_opt = 0;
2182
2183 if (pjsua_var.media_cfg.quality >= 3 &&
2184 pjsua_var.media_cfg.quality <= 4)
2185 {
Nanang Izzuddine85a1832008-05-29 11:03:23 +00002186 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
Benny Prijono50f19b32008-03-11 13:15:43 +00002187 }
2188 else if (pjsua_var.media_cfg.quality < 3) {
Nanang Izzuddine85a1832008-05-29 11:03:23 +00002189 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
Benny Prijono50f19b32008-03-11 13:15:43 +00002190 }
2191
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002192 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
Benny Prijono50f19b32008-03-11 13:15:43 +00002193 conf_port,
2194 selected_clock_rate,
2195 resample_opt,
2196 &resample_port);
2197 if (status != PJ_SUCCESS) {
2198 pj_strerror(status, errmsg, sizeof(errmsg));
2199 PJ_LOG(4, (THIS_FILE,
2200 "Error creating resample port, trying next "
2201 "clock rate",
2202 errmsg));
2203 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2204 pjsua_var.snd_port = NULL;
2205 continue;
2206 } else {
2207 conf_port = resample_port;
2208 break;
2209 }
2210
2211 } else {
2212 break;
2213 }
Benny Prijono658a1c52006-10-11 21:56:16 +00002214 }
Benny Prijono26056d82006-10-11 16:03:41 +00002215
2216 pj_strerror(status, errmsg, sizeof(errmsg));
Benny Prijono658a1c52006-10-11 21:56:16 +00002217 PJ_LOG(4, (THIS_FILE, "..failed: %s", errmsg));
Benny Prijono26056d82006-10-11 16:03:41 +00002218 }
2219
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002220 if (status != PJ_SUCCESS) {
2221 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
2222 return status;
2223 }
2224
Benny Prijonof20687a2006-08-04 18:27:19 +00002225 /* Set AEC */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002226 pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.snd_pool,
Benny Prijono5da50432006-08-07 10:24:52 +00002227 pjsua_var.media_cfg.ec_tail_len,
2228 pjsua_var.media_cfg.ec_options);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002229
Benny Prijono52a93912006-08-04 20:54:37 +00002230 /* Connect sound port to the bridge */
2231 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
2232 conf_port );
2233 if (status != PJ_SUCCESS) {
2234 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
2235 "sound device", status);
2236 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2237 pjsua_var.snd_port = NULL;
2238 return status;
2239 }
2240
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002241 /* Save the device IDs */
2242 pjsua_var.cap_dev = capture_dev;
2243 pjsua_var.play_dev = playback_dev;
2244
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002245 /* Update sound device name. */
2246 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2247 pjmedia_snd_stream_get_info(strm, &si);
2248 play_info = pjmedia_snd_get_dev_info(si.rec_id);
2249
Benny Prijonof3758ee2008-02-26 15:32:16 +00002250 if (si.clock_rate != pjsua_var.media_cfg.clock_rate) {
2251 char tmp_buf[128];
2252 int tmp_buf_len = sizeof(tmp_buf);
2253
2254 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1, "%s (%dKHz)",
2255 play_info->name, si.clock_rate/1000);
2256 pj_strset(&tmp, tmp_buf, tmp_buf_len);
2257 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
2258 } else {
2259 pjmedia_conf_set_port0_name(pjsua_var.mconf,
2260 pj_cstr(&tmp, play_info->name));
2261 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002262
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002263 return PJ_SUCCESS;
2264}
2265
2266
2267/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00002268 * Get currently active sound devices. If sound devices has not been created
2269 * (for example when pjsua_start() is not called), it is possible that
2270 * the function returns PJ_SUCCESS with -1 as device IDs.
2271 */
2272PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
2273 int *playback_dev)
2274{
2275 if (capture_dev) {
2276 *capture_dev = pjsua_var.cap_dev;
2277 }
2278 if (playback_dev) {
2279 *playback_dev = pjsua_var.play_dev;
2280 }
2281
2282 return PJ_SUCCESS;
2283}
2284
2285
2286/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002287 * Use null sound device.
2288 */
2289PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
2290{
2291 pjmedia_port *conf_port;
2292 pj_status_t status;
2293
2294 /* Close existing sound device */
2295 close_snd_dev();
2296
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002297 /* Create memory pool for sound device. */
2298 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2299 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2300
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002301 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
2302
2303 /* Get the port0 of the conference bridge. */
2304 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2305 pj_assert(conf_port != NULL);
2306
2307 /* Create master port, connecting port0 of the conference bridge to
2308 * a null port.
2309 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002310 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002311 conf_port, 0, &pjsua_var.null_snd);
2312 if (status != PJ_SUCCESS) {
2313 pjsua_perror(THIS_FILE, "Unable to create null sound device",
2314 status);
2315 return status;
2316 }
2317
2318 /* Start the master port */
2319 status = pjmedia_master_port_start(pjsua_var.null_snd);
2320 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2321
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002322 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2323 pjsua_var.play_dev = NULL_SND_DEV_ID;
2324
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002325 return PJ_SUCCESS;
2326}
2327
2328
Benny Prijonoe909eac2006-07-27 22:04:56 +00002329
2330/*
2331 * Use no device!
2332 */
2333PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2334{
2335 /* Close existing sound device */
2336 close_snd_dev();
2337
2338 pjsua_var.no_snd = PJ_TRUE;
2339 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2340}
2341
2342
Benny Prijonof20687a2006-08-04 18:27:19 +00002343/*
2344 * Configure the AEC settings of the sound port.
2345 */
Benny Prijono5da50432006-08-07 10:24:52 +00002346PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00002347{
2348 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2349
2350 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00002351 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
2352 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00002353
2354 return PJ_SUCCESS;
2355}
2356
2357
2358/*
2359 * Get current AEC tail length.
2360 */
Benny Prijono22dfe592006-08-06 12:07:13 +00002361PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00002362{
2363 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2364 return PJ_SUCCESS;
2365}
2366
Benny Prijonoe909eac2006-07-27 22:04:56 +00002367
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002368/*****************************************************************************
2369 * Codecs.
2370 */
2371
2372/*
2373 * Enum all supported codecs in the system.
2374 */
2375PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
2376 unsigned *p_count )
2377{
2378 pjmedia_codec_mgr *codec_mgr;
2379 pjmedia_codec_info info[32];
2380 unsigned i, count, prio[32];
2381 pj_status_t status;
2382
2383 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2384 count = PJ_ARRAY_SIZE(info);
2385 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
2386 if (status != PJ_SUCCESS) {
2387 *p_count = 0;
2388 return status;
2389 }
2390
2391 if (count > *p_count) count = *p_count;
2392
2393 for (i=0; i<count; ++i) {
2394 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
2395 id[i].codec_id = pj_str(id[i].buf_);
2396 id[i].priority = (pj_uint8_t) prio[i];
2397 }
2398
2399 *p_count = count;
2400
2401 return PJ_SUCCESS;
2402}
2403
2404
2405/*
2406 * Change codec priority.
2407 */
2408PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
2409 pj_uint8_t priority )
2410{
Benny Prijono88accae2008-06-26 15:48:14 +00002411 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002412 pjmedia_codec_mgr *codec_mgr;
2413
2414 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2415
Benny Prijono88accae2008-06-26 15:48:14 +00002416 if (codec_id->slen==1 && *codec_id->ptr=='*')
2417 codec_id = &all;
2418
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002419 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
2420 priority);
2421}
2422
2423
2424/*
2425 * Get codec parameters.
2426 */
2427PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
2428 pjmedia_codec_param *param )
2429{
Benny Prijono88accae2008-06-26 15:48:14 +00002430 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002431 const pjmedia_codec_info *info;
2432 pjmedia_codec_mgr *codec_mgr;
2433 unsigned count = 1;
2434 pj_status_t status;
2435
2436 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2437
Benny Prijono88accae2008-06-26 15:48:14 +00002438 if (codec_id->slen==1 && *codec_id->ptr=='*')
2439 codec_id = &all;
2440
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002441 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
2442 &count, &info, NULL);
2443 if (status != PJ_SUCCESS)
2444 return status;
2445
2446 if (count != 1)
2447 return PJ_ENOTFOUND;
2448
2449 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
2450 return status;
2451}
2452
2453
2454/*
2455 * Set codec parameters.
2456 */
2457PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
2458 const pjmedia_codec_param *param)
2459{
Benny Prijono00cae612006-07-31 15:19:36 +00002460 PJ_UNUSED_ARG(id);
2461 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002462 PJ_TODO(set_codec_param);
2463 return PJ_SUCCESS;
2464}