blob: 0f4547b9e7443c60e938f57fd43845342b1a3d3a [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
Benny Prijonoa771a512007-02-19 01:13:53 +00003 * Copyright (C) 2003-2007 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
Benny Prijonoeebe9af2006-06-13 22:57:13 +000025#define DEFAULT_RTP_PORT 4000
26
Benny Prijonode479562007-03-15 10:23:55 +000027/* Next RTP port to be used */
28static pj_uint16_t next_rtp_port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000029
30/* Close existing sound device */
31static void close_snd_dev(void);
32
33
34/**
35 * Init media subsystems.
36 */
37pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
38{
Benny Prijonoba5926a2007-05-02 11:29:37 +000039 pj_str_t codec_id = {NULL, 0};
Benny Prijono0498d902006-06-19 14:49:14 +000040 unsigned opt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000041 pj_status_t status;
42
Benny Prijonofc24e692007-01-27 18:31:51 +000043 /* To suppress warning about unused var when all codecs are disabled */
44 PJ_UNUSED_ARG(codec_id);
45
Benny Prijonoeebe9af2006-06-13 22:57:13 +000046 /* Copy configuration */
47 pj_memcpy(&pjsua_var.media_cfg, cfg, sizeof(*cfg));
48
49 /* Normalize configuration */
Benny Prijonoeebe9af2006-06-13 22:57:13 +000050
51 if (pjsua_var.media_cfg.has_ioqueue &&
52 pjsua_var.media_cfg.thread_cnt == 0)
53 {
54 pjsua_var.media_cfg.thread_cnt = 1;
55 }
56
57 if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
58 pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
59 }
60
61 /* Create media endpoint. */
62 status = pjmedia_endpt_create(&pjsua_var.cp.factory,
63 pjsua_var.media_cfg.has_ioqueue? NULL :
64 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
65 pjsua_var.media_cfg.thread_cnt,
66 &pjsua_var.med_endpt);
67 if (status != PJ_SUCCESS) {
68 pjsua_perror(THIS_FILE,
69 "Media stack initialization has returned error",
70 status);
71 return status;
72 }
73
74 /* Register all codecs */
75#if PJMEDIA_HAS_SPEEX_CODEC
76 /* Register speex. */
77 status = pjmedia_codec_speex_init(pjsua_var.med_endpt,
Benny Prijono7ca96da2006-08-07 12:11:40 +000078 0,
Benny Prijono0498d902006-06-19 14:49:14 +000079 pjsua_var.media_cfg.quality,
80 pjsua_var.media_cfg.quality);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000081 if (status != PJ_SUCCESS) {
82 pjsua_perror(THIS_FILE, "Error initializing Speex codec",
83 status);
84 return status;
85 }
Benny Prijono7ca96da2006-08-07 12:11:40 +000086
87 /* Set speex/16000 to higher priority*/
88 codec_id = pj_str("speex/16000");
89 pjmedia_codec_mgr_set_codec_priority(
90 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
91 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
92
93 /* Set speex/8000 to next higher priority*/
94 codec_id = pj_str("speex/8000");
95 pjmedia_codec_mgr_set_codec_priority(
96 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
97 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
98
99
100
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000101#endif /* PJMEDIA_HAS_SPEEX_CODEC */
102
Benny Prijono00cae612006-07-31 15:19:36 +0000103#if PJMEDIA_HAS_ILBC_CODEC
104 /* Register iLBC. */
105 status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt,
106 pjsua_var.media_cfg.ilbc_mode);
107 if (status != PJ_SUCCESS) {
108 pjsua_perror(THIS_FILE, "Error initializing iLBC codec",
109 status);
110 return status;
111 }
112#endif /* PJMEDIA_HAS_ILBC_CODEC */
113
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000114#if PJMEDIA_HAS_GSM_CODEC
115 /* Register GSM */
116 status = pjmedia_codec_gsm_init(pjsua_var.med_endpt);
117 if (status != PJ_SUCCESS) {
118 pjsua_perror(THIS_FILE, "Error initializing GSM codec",
119 status);
120 return status;
121 }
122#endif /* PJMEDIA_HAS_GSM_CODEC */
123
124#if PJMEDIA_HAS_G711_CODEC
125 /* Register PCMA and PCMU */
126 status = pjmedia_codec_g711_init(pjsua_var.med_endpt);
127 if (status != PJ_SUCCESS) {
128 pjsua_perror(THIS_FILE, "Error initializing G711 codec",
129 status);
130 return status;
131 }
132#endif /* PJMEDIA_HAS_G711_CODEC */
133
134#if PJMEDIA_HAS_L16_CODEC
135 /* Register L16 family codecs, but disable all */
136 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
137 if (status != PJ_SUCCESS) {
138 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
139 status);
140 return status;
141 }
142
143 /* Disable ALL L16 codecs */
144 codec_id = pj_str("L16");
145 pjmedia_codec_mgr_set_codec_priority(
146 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
147 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
148
149#endif /* PJMEDIA_HAS_L16_CODEC */
150
151
152 /* Save additional conference bridge parameters for future
153 * reference.
154 */
155 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
Benny Prijonocf0b4b22007-10-06 17:31:09 +0000156 pjsua_var.media_cfg.audio_frame_ptime /
157 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000158 pjsua_var.mconf_cfg.channel_count = 1;
159 pjsua_var.mconf_cfg.bits_per_sample = 16;
160
Benny Prijono0498d902006-06-19 14:49:14 +0000161 /* Init options for conference bridge. */
162 opt = PJMEDIA_CONF_NO_DEVICE;
163 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000164 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000165 {
166 opt |= PJMEDIA_CONF_SMALL_FILTER;
167 }
168 else if (pjsua_var.media_cfg.quality < 3) {
169 opt |= PJMEDIA_CONF_USE_LINEAR;
170 }
171
172
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000173 /* Init conference bridge. */
174 status = pjmedia_conf_create(pjsua_var.pool,
175 pjsua_var.media_cfg.max_media_ports,
176 pjsua_var.media_cfg.clock_rate,
177 pjsua_var.mconf_cfg.channel_count,
178 pjsua_var.mconf_cfg.samples_per_frame,
179 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000180 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000181 if (status != PJ_SUCCESS) {
182 pjsua_perror(THIS_FILE,
183 "Media stack initialization has returned error",
184 status);
185 return status;
186 }
187
188 /* Create null port just in case user wants to use null sound. */
189 status = pjmedia_null_port_create(pjsua_var.pool,
190 pjsua_var.media_cfg.clock_rate,
191 pjsua_var.mconf_cfg.channel_count,
192 pjsua_var.mconf_cfg.samples_per_frame,
193 pjsua_var.mconf_cfg.bits_per_sample,
194 &pjsua_var.null_port);
195 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
196
197 return PJ_SUCCESS;
198}
199
200
201/*
202 * Create RTP and RTCP socket pair, and possibly resolve their public
203 * address via STUN.
204 */
205static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
206 pjmedia_sock_info *skinfo)
207{
208 enum {
209 RTP_RETRY = 100
210 };
211 int i;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000212 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000213 pj_sockaddr_in mapped_addr[2];
214 pj_status_t status = PJ_SUCCESS;
215 pj_sock_t sock[2];
216
Benny Prijonoc97608e2007-03-23 16:34:20 +0000217 /* Make sure STUN server resolution has completed */
218 status = pjsua_resolve_stun_server(PJ_TRUE);
219 if (status != PJ_SUCCESS) {
220 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
221 return status;
222 }
223
224
Benny Prijonode479562007-03-15 10:23:55 +0000225 if (next_rtp_port == 0)
226 next_rtp_port = (pj_uint16_t)cfg->port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000227
228 for (i=0; i<2; ++i)
229 sock[i] = PJ_INVALID_SOCKET;
230
Benny Prijono0a5cad82006-09-26 13:21:02 +0000231 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
232 if (cfg->bound_addr.slen) {
233 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
234 if (status != PJ_SUCCESS) {
235 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
236 status);
237 return status;
238 }
239 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000240
241 /* Loop retry to bind RTP and RTCP sockets. */
Benny Prijonode479562007-03-15 10:23:55 +0000242 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000243
244 /* Create and bind RTP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000245 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000246 if (status != PJ_SUCCESS) {
247 pjsua_perror(THIS_FILE, "socket() error", status);
248 return status;
249 }
250
Benny Prijono0a5cad82006-09-26 13:21:02 +0000251 status = pj_sock_bind_in(sock[0], bound_addr.sin_addr.s_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000252 next_rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000253 if (status != PJ_SUCCESS) {
254 pj_sock_close(sock[0]);
255 sock[0] = PJ_INVALID_SOCKET;
256 continue;
257 }
258
259 /* Create and bind RTCP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000260 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000261 if (status != PJ_SUCCESS) {
262 pjsua_perror(THIS_FILE, "socket() error", status);
263 pj_sock_close(sock[0]);
264 return status;
265 }
266
Benny Prijono0a5cad82006-09-26 13:21:02 +0000267 status = pj_sock_bind_in(sock[1], bound_addr.sin_addr.s_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000268 (pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000269 if (status != PJ_SUCCESS) {
270 pj_sock_close(sock[0]);
271 sock[0] = PJ_INVALID_SOCKET;
272
273 pj_sock_close(sock[1]);
274 sock[1] = PJ_INVALID_SOCKET;
275 continue;
276 }
277
278 /*
279 * If we're configured to use STUN, then find out the mapped address,
280 * and make sure that the mapped RTCP port is adjacent with the RTP.
281 */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000282 if (pjsua_var.stun_srv.addr.sa_family != 0) {
283 char ip_addr[32];
284 pj_str_t stun_srv;
285
286 pj_ansi_strcpy(ip_addr,
287 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
288 stun_srv = pj_str(ip_addr);
289
Benny Prijono14c2b862007-02-21 00:40:05 +0000290 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000291 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
292 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000293 mapped_addr);
294 if (status != PJ_SUCCESS) {
295 pjsua_perror(THIS_FILE, "STUN resolve error", status);
296 goto on_error;
297 }
298
299 if (pj_ntohs(mapped_addr[1].sin_port) ==
300 pj_ntohs(mapped_addr[0].sin_port)+1)
301 {
302 /* Success! */
303 break;
304 }
305
306 pj_sock_close(sock[0]);
307 sock[0] = PJ_INVALID_SOCKET;
308
309 pj_sock_close(sock[1]);
310 sock[1] = PJ_INVALID_SOCKET;
311
Benny Prijono0a5cad82006-09-26 13:21:02 +0000312 } else if (cfg->public_addr.slen) {
313
314 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000315 (pj_uint16_t)next_rtp_port);
Benny Prijono0a5cad82006-09-26 13:21:02 +0000316 if (status != PJ_SUCCESS)
317 goto on_error;
318
319 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000320 (pj_uint16_t)(next_rtp_port+1));
Benny Prijono0a5cad82006-09-26 13:21:02 +0000321 if (status != PJ_SUCCESS)
322 goto on_error;
323
324 break;
325
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000326 } else {
Benny Prijono594e4c52006-09-14 18:51:01 +0000327 pj_in_addr addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000328
329 /* Get local IP address. */
Benny Prijono594e4c52006-09-14 18:51:01 +0000330 status = pj_gethostip(&addr);
331 if (status != PJ_SUCCESS)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000332 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000333
334 for (i=0; i<2; ++i)
Benny Prijono594e4c52006-09-14 18:51:01 +0000335 mapped_addr[i].sin_addr = addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000336
Benny Prijonode479562007-03-15 10:23:55 +0000337 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
338 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000339 break;
340 }
341 }
342
343 if (sock[0] == PJ_INVALID_SOCKET) {
344 PJ_LOG(1,(THIS_FILE,
345 "Unable to find appropriate RTP/RTCP ports combination"));
346 goto on_error;
347 }
348
349
350 skinfo->rtp_sock = sock[0];
351 pj_memcpy(&skinfo->rtp_addr_name,
352 &mapped_addr[0], sizeof(pj_sockaddr_in));
353
354 skinfo->rtcp_sock = sock[1];
355 pj_memcpy(&skinfo->rtcp_addr_name,
356 &mapped_addr[1], sizeof(pj_sockaddr_in));
357
358 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d",
359 pj_inet_ntoa(skinfo->rtp_addr_name.sin_addr),
360 pj_ntohs(skinfo->rtp_addr_name.sin_port)));
361 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s:%d",
362 pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr),
363 pj_ntohs(skinfo->rtcp_addr_name.sin_port)));
364
Benny Prijonode479562007-03-15 10:23:55 +0000365 next_rtp_port += 2;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000366 return PJ_SUCCESS;
367
368on_error:
369 for (i=0; i<2; ++i) {
370 if (sock[i] != PJ_INVALID_SOCKET)
371 pj_sock_close(sock[i]);
372 }
373 return status;
374}
375
376
377/*
378 * Start pjsua media subsystem.
379 */
380pj_status_t pjsua_media_subsys_start(void)
381{
382 pj_status_t status;
383
384 /* Create media for calls, if none is specified */
385 if (pjsua_var.calls[0].med_tp == NULL) {
386 pjsua_transport_config transport_cfg;
387
388 /* Create default transport config */
389 pjsua_transport_config_default(&transport_cfg);
390 transport_cfg.port = DEFAULT_RTP_PORT;
391
392 status = pjsua_media_transports_create(&transport_cfg);
393 if (status != PJ_SUCCESS)
394 return status;
395 }
396
397 /* Create sound port if none is created yet */
Benny Prijonoe909eac2006-07-27 22:04:56 +0000398 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
399 !pjsua_var.no_snd)
400 {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000401 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
402 if (status != PJ_SUCCESS) {
Benny Prijonod639efa2007-04-05 22:45:06 +0000403 pjsua_perror(THIS_FILE, "Error opening sound device", status);
404 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000405 }
406 }
407
408 return PJ_SUCCESS;
409}
410
411
412/*
413 * Destroy pjsua media subsystem.
414 */
415pj_status_t pjsua_media_subsys_destroy(void)
416{
417 unsigned i;
418
419 close_snd_dev();
420
421 if (pjsua_var.mconf) {
422 pjmedia_conf_destroy(pjsua_var.mconf);
423 pjsua_var.mconf = NULL;
424 }
425
426 if (pjsua_var.null_port) {
427 pjmedia_port_destroy(pjsua_var.null_port);
428 pjsua_var.null_port = NULL;
429 }
430
431 /* Destroy file players */
432 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
433 if (pjsua_var.player[i].port) {
434 pjmedia_port_destroy(pjsua_var.player[i].port);
435 pjsua_var.player[i].port = NULL;
436 }
437 }
438
439 /* Destroy file recorders */
440 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
441 if (pjsua_var.recorder[i].port) {
442 pjmedia_port_destroy(pjsua_var.recorder[i].port);
443 pjsua_var.recorder[i].port = NULL;
444 }
445 }
446
447 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000448 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000449 if (pjsua_var.calls[i].med_tp) {
450 (*pjsua_var.calls[i].med_tp->op->destroy)(pjsua_var.calls[i].med_tp);
451 pjsua_var.calls[i].med_tp = NULL;
452 }
453 }
454
455 /* Destroy media endpoint. */
456 if (pjsua_var.med_endpt) {
457
458 /* Shutdown all codecs: */
459# if PJMEDIA_HAS_SPEEX_CODEC
460 pjmedia_codec_speex_deinit();
461# endif /* PJMEDIA_HAS_SPEEX_CODEC */
462
463# if PJMEDIA_HAS_GSM_CODEC
464 pjmedia_codec_gsm_deinit();
465# endif /* PJMEDIA_HAS_GSM_CODEC */
466
467# if PJMEDIA_HAS_G711_CODEC
468 pjmedia_codec_g711_deinit();
469# endif /* PJMEDIA_HAS_G711_CODEC */
470
471# if PJMEDIA_HAS_L16_CODEC
472 pjmedia_codec_l16_deinit();
473# endif /* PJMEDIA_HAS_L16_CODEC */
474
475
476 pjmedia_endpt_destroy(pjsua_var.med_endpt);
477 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000478
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000479 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000480 // Not necessary, as pjmedia_snd_deinit() should have been called
481 // in pjmedia_endpt_destroy().
482 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000483 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000484
Benny Prijonode479562007-03-15 10:23:55 +0000485 /* Reset RTP port */
486 next_rtp_port = 0;
487
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000488 return PJ_SUCCESS;
489}
490
491
Benny Prijonoc97608e2007-03-23 16:34:20 +0000492/* Create normal UDP media transports */
493static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000494{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000495 unsigned i;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000496 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000497 pj_status_t status;
498
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000499 /* Create each media transport */
500 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
501
Benny Prijono617c5bc2007-04-02 19:51:21 +0000502 status = create_rtp_rtcp_sock(cfg, &skinfo);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000503 if (status != PJ_SUCCESS) {
504 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
505 status);
506 goto on_error;
507 }
508 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000509 &skinfo, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000510 &pjsua_var.calls[i].med_tp);
511 if (status != PJ_SUCCESS) {
512 pjsua_perror(THIS_FILE, "Unable to create media transport",
513 status);
514 goto on_error;
515 }
Benny Prijono00cae612006-07-31 15:19:36 +0000516
517 pjmedia_transport_udp_simulate_lost(pjsua_var.calls[i].med_tp,
518 PJMEDIA_DIR_ENCODING,
519 pjsua_var.media_cfg.tx_drop_pct);
520
521 pjmedia_transport_udp_simulate_lost(pjsua_var.calls[i].med_tp,
522 PJMEDIA_DIR_DECODING,
523 pjsua_var.media_cfg.rx_drop_pct);
524
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000525 }
526
Benny Prijonoc97608e2007-03-23 16:34:20 +0000527 return PJ_SUCCESS;
528
529on_error:
530 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
531 if (pjsua_var.calls[i].med_tp != NULL) {
532 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
533 pjsua_var.calls[i].med_tp = NULL;
534 }
535 }
536
537 return status;
538}
539
540
Benny Prijono096c56c2007-09-15 08:30:16 +0000541/* This callback is called when ICE negotiation completes */
542static void on_ice_complete(pjmedia_transport *tp, pj_status_t result)
543{
544 unsigned id, c;
545 pj_bool_t found = PJ_FALSE;
546
547 /* We're only interested with failure case */
548 if (result == PJ_SUCCESS)
549 return;
550
551 /* Find call which has this media transport */
552
553 PJSUA_LOCK();
554
555 for (id=0, c=0; id<PJSUA_MAX_CALLS && c<pjsua_var.call_cnt; ++id) {
556 pjsua_call *call = &pjsua_var.calls[id];
557 if (call->inv) {
558 ++c;
559
560 if (call->med_tp == tp) {
561 call->media_st = PJSUA_CALL_MEDIA_ERROR;
562 call->media_dir = PJMEDIA_DIR_NONE;
563 found = PJ_TRUE;
564 break;
565 }
566 }
567 }
568
569 PJSUA_UNLOCK();
570
571 if (found && pjsua_var.ua_cfg.cb.on_call_media_state) {
572 pjsua_var.ua_cfg.cb.on_call_media_state(id);
573 }
574}
575
576
Benny Prijonoc97608e2007-03-23 16:34:20 +0000577/* Create ICE media transports (when ice is enabled) */
578static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
579{
580 unsigned i;
Benny Prijonob681a2f2007-03-25 18:44:51 +0000581 pj_sockaddr_in addr;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000582 pj_status_t status;
583
Benny Prijonoda9785b2007-04-02 20:43:06 +0000584 /* Make sure STUN server resolution has completed */
585 status = pjsua_resolve_stun_server(PJ_TRUE);
586 if (status != PJ_SUCCESS) {
587 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
588 return status;
589 }
590
Benny Prijonob681a2f2007-03-25 18:44:51 +0000591 pj_sockaddr_in_init(&addr, 0, (pj_uint16_t)cfg->port);
592
Benny Prijonoc97608e2007-03-23 16:34:20 +0000593 /* Create each media transport */
594 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijonoa6bd7582007-03-28 15:49:48 +0000595 pj_ice_strans_comp comp;
Benny Prijono096c56c2007-09-15 08:30:16 +0000596 pjmedia_ice_cb ice_cb;
Benny Prijonob681a2f2007-03-25 18:44:51 +0000597 int next_port;
Benny Prijonodc1fe222007-06-26 10:13:13 +0000598#if PJMEDIA_ADVERTISE_RTCP
599 enum { COMP_CNT=2 };
600#else
601 enum { COMP_CNT=1 };
602#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000603
Benny Prijono096c56c2007-09-15 08:30:16 +0000604 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
605 ice_cb.on_ice_complete = &on_ice_complete;
606
Benny Prijonodc1fe222007-06-26 10:13:13 +0000607 status = pjmedia_ice_create(pjsua_var.med_endpt, NULL, COMP_CNT,
Benny Prijono096c56c2007-09-15 08:30:16 +0000608 &pjsua_var.stun_cfg, &ice_cb,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000609 &pjsua_var.calls[i].med_tp);
610 if (status != PJ_SUCCESS) {
611 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
612 status);
613 goto on_error;
614 }
615
Benny Prijono11da9bc2007-09-15 08:55:00 +0000616 pjmedia_ice_simulate_lost(pjsua_var.calls[i].med_tp,
617 PJMEDIA_DIR_ENCODING,
618 pjsua_var.media_cfg.tx_drop_pct);
619
620 pjmedia_ice_simulate_lost(pjsua_var.calls[i].med_tp,
621 PJMEDIA_DIR_DECODING,
622 pjsua_var.media_cfg.rx_drop_pct);
623
Benny Prijonob681a2f2007-03-25 18:44:51 +0000624 status = pjmedia_ice_start_init(pjsua_var.calls[i].med_tp, 0, &addr,
625 &pjsua_var.stun_srv.ipv4, NULL);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000626 if (status != PJ_SUCCESS) {
Benny Prijonob681a2f2007-03-25 18:44:51 +0000627 pjsua_perror(THIS_FILE, "Error starting ICE transport",
Benny Prijonoc97608e2007-03-23 16:34:20 +0000628 status);
629 goto on_error;
630 }
631
Benny Prijonob681a2f2007-03-25 18:44:51 +0000632 pjmedia_ice_get_comp(pjsua_var.calls[i].med_tp, 1, &comp);
633 next_port = pj_ntohs(comp.local_addr.ipv4.sin_port);
634 next_port += 2;
635 addr.sin_port = pj_htons((pj_uint16_t)next_port);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000636 }
637
638 /* Wait until all ICE transports are ready */
639 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000640
641 /* Wait until interface status is PJ_SUCCESS */
642 for (;;) {
Benny Prijonob681a2f2007-03-25 18:44:51 +0000643 status = pjmedia_ice_get_init_status(pjsua_var.calls[i].med_tp);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000644 if (status == PJ_EPENDING)
645 pjsua_handle_events(100);
646 else
647 break;
648 }
649
650 if (status != PJ_SUCCESS) {
651 pjsua_perror(THIS_FILE,
652 "Error adding STUN address to ICE media transport",
653 status);
654 goto on_error;
655 }
656
Benny Prijonoc97608e2007-03-23 16:34:20 +0000657 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000658
659 return PJ_SUCCESS;
660
661on_error:
662 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
663 if (pjsua_var.calls[i].med_tp != NULL) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000664 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000665 pjsua_var.calls[i].med_tp = NULL;
666 }
667 }
668
Benny Prijonoc97608e2007-03-23 16:34:20 +0000669 return status;
670}
671
672
673/*
674 * Create UDP media transports for all the calls. This function creates
675 * one UDP media transport for each call.
676 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +0000677PJ_DEF(pj_status_t) pjsua_media_transports_create(
678 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000679{
680 pjsua_transport_config cfg;
681 unsigned i;
682 pj_status_t status;
683
684
685 /* Make sure pjsua_init() has been called */
686 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
687
688 PJSUA_LOCK();
689
690 /* Delete existing media transports */
691 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
692 if (pjsua_var.calls[i].med_tp != NULL) {
693 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
694 pjsua_var.calls[i].med_tp = NULL;
695 }
696 }
697
698 /* Copy config */
699 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
700
701 if (pjsua_var.media_cfg.enable_ice) {
702 status = create_ice_media_transports(&cfg);
703 } else {
704 status = create_udp_media_transports(&cfg);
705 }
706
707
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000708 PJSUA_UNLOCK();
709
710 return status;
711}
712
713
Benny Prijonoc97608e2007-03-23 16:34:20 +0000714pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
715 pjsip_role_e role)
716{
717 pjsua_call *call = &pjsua_var.calls[call_id];
718
719 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijonoa6bd7582007-03-28 15:49:48 +0000720 pj_ice_sess_role ice_role;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000721 pj_status_t status;
722
Benny Prijonoa6bd7582007-03-28 15:49:48 +0000723 ice_role = (role==PJSIP_ROLE_UAC ? PJ_ICE_SESS_ROLE_CONTROLLING :
724 PJ_ICE_SESS_ROLE_CONTROLLED);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000725
726 /* Restart ICE */
727 pjmedia_ice_stop_ice(call->med_tp);
728
729 status = pjmedia_ice_init_ice(call->med_tp, ice_role, NULL, NULL);
730 if (status != PJ_SUCCESS)
731 return status;
732 }
733
734 return PJ_SUCCESS;
735}
736
737pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
738 pj_pool_t *pool,
739 pjmedia_sdp_session **p_sdp)
740{
741 pjmedia_sdp_session *sdp;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000742 pjmedia_sock_info skinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000743 pjsua_call *call = &pjsua_var.calls[call_id];
744 pj_status_t status;
745
Benny Prijono55e82352007-05-10 20:49:08 +0000746 /* Return error if media transport has not been created yet
747 * (e.g. application is starting)
748 */
749 if (call->med_tp == NULL) {
750 return PJ_EBUSY;
751 }
752
Benny Prijono617c5bc2007-04-02 19:51:21 +0000753 /* Get media socket info */
754 pjmedia_transport_get_info(call->med_tp, &skinfo);
755
756 /* Create SDP */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000757 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, 1,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000758 &skinfo, &sdp);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000759 if (status != PJ_SUCCESS)
760 goto on_error;
761
762 if (pjsua_var.media_cfg.enable_ice) {
763 status = pjmedia_ice_modify_sdp(call->med_tp, pool, sdp);
764 if (status != PJ_SUCCESS)
765 goto on_error;
766 }
767
768 *p_sdp = sdp;
769 return PJ_SUCCESS;
770
771on_error:
772 pjsua_media_channel_deinit(call_id);
773 return status;
774
775}
776
777
778static void stop_media_session(pjsua_call_id call_id)
779{
780 pjsua_call *call = &pjsua_var.calls[call_id];
781
782 if (call->conf_slot != PJSUA_INVALID_ID) {
783 pjmedia_conf_remove_port(pjsua_var.mconf, call->conf_slot);
784 call->conf_slot = PJSUA_INVALID_ID;
785 }
786
787 if (call->session) {
788 pjmedia_session_destroy(call->session);
789 call->session = NULL;
790
791 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
792 call_id));
793
794 }
795
796 call->media_st = PJSUA_CALL_MEDIA_NONE;
797}
798
799pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
800{
801 pjsua_call *call = &pjsua_var.calls[call_id];
802
803 stop_media_session(call_id);
804
805 if (pjsua_var.media_cfg.enable_ice) {
806 pjmedia_ice_stop_ice(call->med_tp);
807 }
808
809 return PJ_SUCCESS;
810}
811
812
813/*
814 * DTMF callback from the stream.
815 */
816static void dtmf_callback(pjmedia_stream *strm, void *user_data,
817 int digit)
818{
819 PJ_UNUSED_ARG(strm);
820
821 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
822 pjsua_call_id call_id;
823
824 call_id = (pjsua_call_id)user_data;
825 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
826 }
827}
828
829
830pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijonodbce2cf2007-03-28 16:24:00 +0000831 const pjmedia_sdp_session *local_sdp,
832 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000833{
834 int prev_media_st = 0;
835 pjsua_call *call = &pjsua_var.calls[call_id];
836 pjmedia_session_info sess_info;
837 pjmedia_port *media_port;
838 pj_str_t port_name;
839 char tmp[PJSIP_MAX_URL_SIZE];
840 pj_status_t status;
841
842 /* Destroy existing media session, if any. */
843 prev_media_st = call->media_st;
844 stop_media_session(call->index);
845
846 /* Create media session info based on SDP parameters.
847 * We only support one stream per session at the moment
848 */
849 status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
850 pjsua_var.med_endpt,
851 1,&sess_info,
852 local_sdp, remote_sdp);
853 if (status != PJ_SUCCESS)
854 return status;
855
856
857 /* Check if media is put on-hold */
858 if (sess_info.stream_cnt == 0 ||
859 sess_info.stream_info[0].dir == PJMEDIA_DIR_NONE)
860 {
861
862 /* Determine who puts the call on-hold */
863 if (prev_media_st == PJSUA_CALL_MEDIA_ACTIVE) {
864 if (pjmedia_sdp_neg_was_answer_remote(call->inv->neg)) {
865 /* It was local who offer hold */
866 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
867 } else {
868 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
869 }
870 }
871
872 call->media_dir = PJMEDIA_DIR_NONE;
873
Benny Prijono667952e2007-04-02 19:27:54 +0000874 /* Shutdown ICE session */
875 if (pjsua_var.media_cfg.enable_ice) {
876 pjmedia_ice_stop_ice(call->med_tp);
877 }
878
Benny Prijonoc97608e2007-03-23 16:34:20 +0000879 /* No need because we need keepalive? */
880
881 } else {
882
883 /* Start ICE */
884 if (pjsua_var.media_cfg.enable_ice) {
885 status = pjmedia_ice_start_ice(call->med_tp, call->inv->pool,
Benny Prijonofd70c9b2007-03-27 11:00:38 +0000886 remote_sdp, 0);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000887 if (status != PJ_SUCCESS)
888 return status;
889 }
890
891 /* Override ptime, if this option is specified. */
892 if (pjsua_var.media_cfg.ptime != 0) {
893 sess_info.stream_info[0].param->setting.frm_per_pkt = (pj_uint8_t)
894 (pjsua_var.media_cfg.ptime / sess_info.stream_info[0].param->info.frm_ptime);
895 if (sess_info.stream_info[0].param->setting.frm_per_pkt == 0)
896 sess_info.stream_info[0].param->setting.frm_per_pkt = 1;
897 }
898
899 /* Disable VAD, if this option is specified. */
900 if (pjsua_var.media_cfg.no_vad) {
901 sess_info.stream_info[0].param->setting.vad = 0;
902 }
903
904
905 /* Optionally, application may modify other stream settings here
906 * (such as jitter buffer parameters, codec ptime, etc.)
907 */
908 sess_info.stream_info[0].jb_init = pjsua_var.media_cfg.jb_init;
909 sess_info.stream_info[0].jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
910 sess_info.stream_info[0].jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
911 sess_info.stream_info[0].jb_max = pjsua_var.media_cfg.jb_max;
912
913 /* Create session based on session info. */
914 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
915 &call->med_tp,
916 call, &call->session );
917 if (status != PJ_SUCCESS) {
918 return status;
919 }
920
921 /* If DTMF callback is installed by application, install our
922 * callback to the session.
923 */
924 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
925 pjmedia_session_set_dtmf_callback(call->session, 0,
926 &dtmf_callback,
927 (void*)(call->index));
928 }
929
930 /* Get the port interface of the first stream in the session.
931 * We need the port interface to add to the conference bridge.
932 */
933 pjmedia_session_get_port(call->session, 0, &media_port);
934
935
936 /*
937 * Add the call to conference bridge.
938 */
939 port_name.ptr = tmp;
940 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
941 call->inv->dlg->remote.info->uri,
942 tmp, sizeof(tmp));
943 if (port_name.slen < 1) {
944 port_name = pj_str("call");
945 }
946 status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
947 media_port,
948 &port_name,
949 (unsigned*)&call->conf_slot);
950 if (status != PJ_SUCCESS) {
951 return status;
952 }
953
954 /* Call's media state is active */
955 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
956 call->media_dir = sess_info.stream_info[0].dir;
957 }
958
959 /* Print info. */
960 {
961 char info[80];
962 int info_len = 0;
963 unsigned i;
964
965 for (i=0; i<sess_info.stream_cnt; ++i) {
966 int len;
967 const char *dir;
968 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
969
970 switch (strm_info->dir) {
971 case PJMEDIA_DIR_NONE:
972 dir = "inactive";
973 break;
974 case PJMEDIA_DIR_ENCODING:
975 dir = "sendonly";
976 break;
977 case PJMEDIA_DIR_DECODING:
978 dir = "recvonly";
979 break;
980 case PJMEDIA_DIR_ENCODING_DECODING:
981 dir = "sendrecv";
982 break;
983 default:
984 dir = "unknown";
985 break;
986 }
987 len = pj_ansi_sprintf( info+info_len,
988 ", stream #%d: %.*s (%s)", i,
989 (int)strm_info->fmt.encoding_name.slen,
990 strm_info->fmt.encoding_name.ptr,
991 dir);
992 if (len > 0)
993 info_len += len;
994 }
995 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
996 }
997
998 return PJ_SUCCESS;
999}
1000
1001
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001002/*
1003 * Get maxinum number of conference ports.
1004 */
1005PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1006{
1007 return pjsua_var.media_cfg.max_media_ports;
1008}
1009
1010
1011/*
1012 * Get current number of active ports in the bridge.
1013 */
1014PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1015{
1016 unsigned ports[256];
1017 unsigned count = PJ_ARRAY_SIZE(ports);
1018 pj_status_t status;
1019
1020 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1021 if (status != PJ_SUCCESS)
1022 count = 0;
1023
1024 return count;
1025}
1026
1027
1028/*
1029 * Enumerate all conference ports.
1030 */
1031PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1032 unsigned *count)
1033{
1034 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1035}
1036
1037
1038/*
1039 * Get information about the specified conference port
1040 */
1041PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1042 pjsua_conf_port_info *info)
1043{
1044 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001045 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001046 pj_status_t status;
1047
1048 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1049 if (status != PJ_SUCCESS)
1050 return status;
1051
Benny Prijonoac623b32006-07-03 15:19:31 +00001052 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001053 info->slot_id = id;
1054 info->name = cinfo.name;
1055 info->clock_rate = cinfo.clock_rate;
1056 info->channel_count = cinfo.channel_count;
1057 info->samples_per_frame = cinfo.samples_per_frame;
1058 info->bits_per_sample = cinfo.bits_per_sample;
1059
1060 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001061 info->listener_cnt = cinfo.listener_cnt;
1062 for (i=0; i<cinfo.listener_cnt; ++i) {
1063 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001064 }
1065
1066 return PJ_SUCCESS;
1067}
1068
1069
1070/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001071 * Add arbitrary media port to PJSUA's conference bridge.
1072 */
1073PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1074 pjmedia_port *port,
1075 pjsua_conf_port_id *p_id)
1076{
1077 pj_status_t status;
1078
1079 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1080 port, NULL, (unsigned*)p_id);
1081 if (status != PJ_SUCCESS) {
1082 if (p_id)
1083 *p_id = PJSUA_INVALID_ID;
1084 }
1085
1086 return status;
1087}
1088
1089
1090/*
1091 * Remove arbitrary slot from the conference bridge.
1092 */
1093PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1094{
1095 return pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1096}
1097
1098
1099/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001100 * Establish unidirectional media flow from souce to sink.
1101 */
1102PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1103 pjsua_conf_port_id sink)
1104{
1105 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
1106}
1107
1108
1109/*
1110 * Disconnect media flow from the source to destination port.
1111 */
1112PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
1113 pjsua_conf_port_id sink)
1114{
1115 return pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
1116}
1117
1118
Benny Prijono6dd967c2006-12-26 02:27:14 +00001119/*
1120 * Adjust the signal level to be transmitted from the bridge to the
1121 * specified port by making it louder or quieter.
1122 */
1123PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
1124 float level)
1125{
1126 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
1127 (int)((level-1) * 128));
1128}
1129
1130/*
1131 * Adjust the signal level to be received from the specified port (to
1132 * the bridge) by making it louder or quieter.
1133 */
1134PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
1135 float level)
1136{
1137 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
1138 (int)((level-1) * 128));
1139}
1140
1141
1142/*
1143 * Get last signal level transmitted to or received from the specified port.
1144 */
1145PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
1146 unsigned *tx_level,
1147 unsigned *rx_level)
1148{
1149 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
1150 tx_level, rx_level);
1151}
1152
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001153/*****************************************************************************
1154 * File player.
1155 */
1156
Benny Prijonod5696da2007-07-17 16:25:45 +00001157static char* get_basename(const char *path, unsigned len)
1158{
1159 char *p = ((char*)path) + len;
1160
1161 if (len==0)
1162 return p;
1163
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001164 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00001165
1166 return (p==path) ? p : p+1;
1167}
1168
1169
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001170/*
1171 * Create a file player, and automatically connect this player to
1172 * the conference bridge.
1173 */
1174PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1175 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001176 pjsua_player_id *p_id)
1177{
1178 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001179 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00001180 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001181 pjmedia_port *port;
1182 pj_status_t status;
1183
1184 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1185 return PJ_ETOOMANY;
1186
1187 PJSUA_LOCK();
1188
1189 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1190 if (pjsua_var.player[file_id].port == NULL)
1191 break;
1192 }
1193
1194 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1195 /* This is unexpected */
1196 PJSUA_UNLOCK();
1197 pj_assert(0);
1198 return PJ_EBUG;
1199 }
1200
1201 pj_memcpy(path, filename->ptr, filename->slen);
1202 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00001203
1204 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1205 if (!pool) {
1206 PJSUA_UNLOCK();
1207 return PJ_ENOMEM;
1208 }
1209
1210 status = pjmedia_wav_player_port_create(pool, path,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001211 pjsua_var.mconf_cfg.samples_per_frame *
1212 1000 / pjsua_var.media_cfg.clock_rate,
Benny Prijono00cae612006-07-31 15:19:36 +00001213 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001214 if (status != PJ_SUCCESS) {
1215 PJSUA_UNLOCK();
1216 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001217 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001218 return status;
1219 }
1220
1221 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
1222 port, filename, &slot);
1223 if (status != PJ_SUCCESS) {
1224 pjmedia_port_destroy(port);
1225 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00001226 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1227 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001228 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001229 return status;
1230 }
1231
Benny Prijonoa66c3312007-01-21 23:12:40 +00001232 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00001233 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001234 pjsua_var.player[file_id].port = port;
1235 pjsua_var.player[file_id].slot = slot;
1236
1237 if (p_id) *p_id = file_id;
1238
1239 ++pjsua_var.player_cnt;
1240
1241 PJSUA_UNLOCK();
1242 return PJ_SUCCESS;
1243}
1244
1245
1246/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00001247 * Create a file playlist media port, and automatically add the port
1248 * to the conference bridge.
1249 */
1250PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
1251 unsigned file_count,
1252 const pj_str_t *label,
1253 unsigned options,
1254 pjsua_player_id *p_id)
1255{
1256 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00001257 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001258 pjmedia_port *port;
1259 pj_status_t status;
1260
1261 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1262 return PJ_ETOOMANY;
1263
1264 PJSUA_LOCK();
1265
1266 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1267 if (pjsua_var.player[file_id].port == NULL)
1268 break;
1269 }
1270
1271 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1272 /* This is unexpected */
1273 PJSUA_UNLOCK();
1274 pj_assert(0);
1275 return PJ_EBUG;
1276 }
1277
1278
1279 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
1280 pjsua_var.media_cfg.clock_rate;
1281
Benny Prijonod5696da2007-07-17 16:25:45 +00001282 pool = pjsua_pool_create("playlist", 1000, 1000);
1283 if (!pool) {
1284 PJSUA_UNLOCK();
1285 return PJ_ENOMEM;
1286 }
1287
1288 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001289 file_names, file_count,
1290 ptime, options, 0, &port);
1291 if (status != PJ_SUCCESS) {
1292 PJSUA_UNLOCK();
1293 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001294 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001295 return status;
1296 }
1297
Benny Prijonod5696da2007-07-17 16:25:45 +00001298 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001299 port, &port->info.name, &slot);
1300 if (status != PJ_SUCCESS) {
1301 pjmedia_port_destroy(port);
1302 PJSUA_UNLOCK();
1303 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001304 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001305 return status;
1306 }
1307
1308 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00001309 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001310 pjsua_var.player[file_id].port = port;
1311 pjsua_var.player[file_id].slot = slot;
1312
1313 if (p_id) *p_id = file_id;
1314
1315 ++pjsua_var.player_cnt;
1316
1317 PJSUA_UNLOCK();
1318 return PJ_SUCCESS;
1319
1320}
1321
1322
1323/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001324 * Get conference port ID associated with player.
1325 */
1326PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
1327{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001328 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001329 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1330
1331 return pjsua_var.player[id].slot;
1332}
1333
Benny Prijono469b1522006-12-26 03:05:17 +00001334/*
1335 * Get the media port for the player.
1336 */
1337PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_recorder_id id,
1338 pjmedia_port **p_port)
1339{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001340 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001341 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1342 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1343
1344 *p_port = pjsua_var.player[id].port;
1345
1346 return PJ_SUCCESS;
1347}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001348
1349/*
1350 * Set playback position.
1351 */
1352PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
1353 pj_uint32_t samples)
1354{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001355 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001356 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001357 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001358
1359 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
1360}
1361
1362
1363/*
1364 * Close the file, remove the player from the bridge, and free
1365 * resources associated with the file player.
1366 */
1367PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
1368{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001369 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001370 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1371
1372 PJSUA_LOCK();
1373
1374 if (pjsua_var.player[id].port) {
1375 pjmedia_conf_remove_port(pjsua_var.mconf,
1376 pjsua_var.player[id].slot);
1377 pjmedia_port_destroy(pjsua_var.player[id].port);
1378 pjsua_var.player[id].port = NULL;
1379 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00001380 pj_pool_release(pjsua_var.player[id].pool);
1381 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001382 pjsua_var.player_cnt--;
1383 }
1384
1385 PJSUA_UNLOCK();
1386
1387 return PJ_SUCCESS;
1388}
1389
1390
1391/*****************************************************************************
1392 * File recorder.
1393 */
1394
1395/*
1396 * Create a file recorder, and automatically connect this recorder to
1397 * the conference bridge.
1398 */
1399PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00001400 unsigned enc_type,
1401 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001402 pj_ssize_t max_size,
1403 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001404 pjsua_recorder_id *p_id)
1405{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001406 enum Format
1407 {
1408 FMT_UNKNOWN,
1409 FMT_WAV,
1410 FMT_MP3,
1411 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001412 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001413 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001414 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00001415 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00001416 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001417 pjmedia_port *port;
1418 pj_status_t status;
1419
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001420 /* Filename must present */
1421 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
1422
Benny Prijono00cae612006-07-31 15:19:36 +00001423 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001424 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001425
Benny Prijono8f310522006-10-20 11:08:49 +00001426 /* Don't support encoding type at present */
1427 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001428
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001429 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
1430 return PJ_ETOOMANY;
1431
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001432 /* Determine the file format */
1433 ext.ptr = filename->ptr + filename->slen - 4;
1434 ext.slen = 4;
1435
1436 if (pj_stricmp2(&ext, ".wav") == 0)
1437 file_format = FMT_WAV;
1438 else if (pj_stricmp2(&ext, ".mp3") == 0)
1439 file_format = FMT_MP3;
1440 else {
1441 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
1442 "determine file format for %.*s",
1443 (int)filename->slen, filename->ptr));
1444 return PJ_ENOTSUP;
1445 }
1446
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001447 PJSUA_LOCK();
1448
1449 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
1450 if (pjsua_var.recorder[file_id].port == NULL)
1451 break;
1452 }
1453
1454 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
1455 /* This is unexpected */
1456 PJSUA_UNLOCK();
1457 pj_assert(0);
1458 return PJ_EBUG;
1459 }
1460
1461 pj_memcpy(path, filename->ptr, filename->slen);
1462 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001463
Benny Prijonod5696da2007-07-17 16:25:45 +00001464 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1465 if (!pool) {
1466 PJSUA_UNLOCK();
1467 return PJ_ENOMEM;
1468 }
1469
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001470 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00001471 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001472 pjsua_var.media_cfg.clock_rate,
1473 pjsua_var.mconf_cfg.channel_count,
1474 pjsua_var.mconf_cfg.samples_per_frame,
1475 pjsua_var.mconf_cfg.bits_per_sample,
1476 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001477 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00001478 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001479 port = NULL;
1480 status = PJ_ENOTSUP;
1481 }
1482
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001483 if (status != PJ_SUCCESS) {
1484 PJSUA_UNLOCK();
1485 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001486 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001487 return status;
1488 }
1489
Benny Prijonod5696da2007-07-17 16:25:45 +00001490 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001491 port, filename, &slot);
1492 if (status != PJ_SUCCESS) {
1493 pjmedia_port_destroy(port);
1494 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00001495 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001496 return status;
1497 }
1498
1499 pjsua_var.recorder[file_id].port = port;
1500 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00001501 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001502
1503 if (p_id) *p_id = file_id;
1504
1505 ++pjsua_var.rec_cnt;
1506
1507 PJSUA_UNLOCK();
1508 return PJ_SUCCESS;
1509}
1510
1511
1512/*
1513 * Get conference port associated with recorder.
1514 */
1515PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
1516{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001517 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1518 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001519 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1520
1521 return pjsua_var.recorder[id].slot;
1522}
1523
Benny Prijono469b1522006-12-26 03:05:17 +00001524/*
1525 * Get the media port for the recorder.
1526 */
1527PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
1528 pjmedia_port **p_port)
1529{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001530 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1531 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001532 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1533 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1534
1535 *p_port = pjsua_var.recorder[id].port;
1536 return PJ_SUCCESS;
1537}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001538
1539/*
1540 * Destroy recorder (this will complete recording).
1541 */
1542PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
1543{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001544 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1545 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001546 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1547
1548 PJSUA_LOCK();
1549
1550 if (pjsua_var.recorder[id].port) {
1551 pjmedia_conf_remove_port(pjsua_var.mconf,
1552 pjsua_var.recorder[id].slot);
1553 pjmedia_port_destroy(pjsua_var.recorder[id].port);
1554 pjsua_var.recorder[id].port = NULL;
1555 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00001556 pj_pool_release(pjsua_var.recorder[id].pool);
1557 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001558 pjsua_var.rec_cnt--;
1559 }
1560
1561 PJSUA_UNLOCK();
1562
1563 return PJ_SUCCESS;
1564}
1565
1566
1567/*****************************************************************************
1568 * Sound devices.
1569 */
1570
1571/*
1572 * Enum sound devices.
1573 */
1574PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
1575 unsigned *count)
1576{
1577 unsigned i, dev_count;
1578
1579 dev_count = pjmedia_snd_get_dev_count();
1580
1581 if (dev_count > *count) dev_count = *count;
1582
1583 for (i=0; i<dev_count; ++i) {
1584 const pjmedia_snd_dev_info *ci;
1585
1586 ci = pjmedia_snd_get_dev_info(i);
1587 pj_memcpy(&info[i], ci, sizeof(*ci));
1588 }
1589
1590 *count = dev_count;
1591
1592 return PJ_SUCCESS;
1593}
1594
1595
1596/* Close existing sound device */
1597static void close_snd_dev(void)
1598{
1599 /* Close sound device */
1600 if (pjsua_var.snd_port) {
1601 const pjmedia_snd_dev_info *cap_info, *play_info;
1602
1603 cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
1604 play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
1605
1606 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
1607 "%s sound capture device",
1608 play_info->name, cap_info->name));
1609
1610 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
1611 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1612 pjsua_var.snd_port = NULL;
1613 }
1614
1615 /* Close null sound device */
1616 if (pjsua_var.null_snd) {
1617 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
1618 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
1619 pjsua_var.null_snd = NULL;
1620 }
1621}
1622
1623/*
1624 * Select or change sound device. Application may call this function at
1625 * any time to replace current sound device.
1626 */
1627PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
1628 int playback_dev)
1629{
1630 pjmedia_port *conf_port;
Benny Prijono6dd967c2006-12-26 02:27:14 +00001631 const pjmedia_snd_dev_info *play_info;
Benny Prijono26056d82006-10-11 16:03:41 +00001632 unsigned clock_rates[] = { 0, 22050, 44100, 48000, 11025, 32000, 8000};
Benny Prijono658a1c52006-10-11 21:56:16 +00001633 unsigned selected_clock_rate = 0;
Benny Prijono26056d82006-10-11 16:03:41 +00001634 unsigned i;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00001635 pjmedia_snd_stream *strm;
1636 pjmedia_snd_stream_info si;
1637 pj_str_t tmp;
Benny Prijono26056d82006-10-11 16:03:41 +00001638 pj_status_t status = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001639
1640 /* Close existing sound port */
1641 close_snd_dev();
1642
1643
Benny Prijono26056d82006-10-11 16:03:41 +00001644 /* Set default clock rate */
1645 clock_rates[0] = pjsua_var.media_cfg.clock_rate;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001646
Benny Prijono26056d82006-10-11 16:03:41 +00001647 /* Attempts to open the sound device with different clock rates */
1648 for (i=0; i<PJ_ARRAY_SIZE(clock_rates); ++i) {
1649 char errmsg[PJ_ERR_MSG_SIZE];
Benny Prijonocf0b4b22007-10-06 17:31:09 +00001650 unsigned fps;
Benny Prijono26056d82006-10-11 16:03:41 +00001651
1652 PJ_LOG(4,(THIS_FILE,
1653 "pjsua_set_snd_dev(): attempting to open devices "
1654 "@%d Hz", clock_rates[i]));
1655
1656 /* Create the sound device. Sound port will start immediately. */
Benny Prijonocf0b4b22007-10-06 17:31:09 +00001657 fps = 1000 / pjsua_var.media_cfg.audio_frame_ptime;
Benny Prijono26056d82006-10-11 16:03:41 +00001658 status = pjmedia_snd_port_create(pjsua_var.pool, capture_dev,
1659 playback_dev,
1660 clock_rates[i], 1,
Benny Prijonocf0b4b22007-10-06 17:31:09 +00001661 clock_rates[i]/fps,
Benny Prijono26056d82006-10-11 16:03:41 +00001662 16, 0, &pjsua_var.snd_port);
1663
Benny Prijono658a1c52006-10-11 21:56:16 +00001664 if (status == PJ_SUCCESS) {
1665 selected_clock_rate = clock_rates[i];
Benny Prijono26056d82006-10-11 16:03:41 +00001666 break;
Benny Prijono658a1c52006-10-11 21:56:16 +00001667 }
Benny Prijono26056d82006-10-11 16:03:41 +00001668
1669 pj_strerror(status, errmsg, sizeof(errmsg));
Benny Prijono658a1c52006-10-11 21:56:16 +00001670 PJ_LOG(4, (THIS_FILE, "..failed: %s", errmsg));
Benny Prijono26056d82006-10-11 16:03:41 +00001671 }
1672
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001673 if (status != PJ_SUCCESS) {
1674 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
1675 return status;
1676 }
1677
1678 /* Get the port0 of the conference bridge. */
1679 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1680 pj_assert(conf_port != NULL);
1681
Benny Prijonof20687a2006-08-04 18:27:19 +00001682 /* Set AEC */
Benny Prijono5da50432006-08-07 10:24:52 +00001683 pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
1684 pjsua_var.media_cfg.ec_tail_len,
1685 pjsua_var.media_cfg.ec_options);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001686
Benny Prijono658a1c52006-10-11 21:56:16 +00001687 /* If there's mismatch between sound port and conference's port,
1688 * create a resample port to bridge them.
1689 */
1690 if (selected_clock_rate != pjsua_var.media_cfg.clock_rate) {
1691 pjmedia_port *resample_port;
1692
1693 status = pjmedia_resample_port_create(pjsua_var.pool, conf_port,
1694 selected_clock_rate, 0,
1695 &resample_port);
1696 if (status != PJ_SUCCESS) {
1697 pjsua_perror("Error creating resample port", THIS_FILE, status);
1698 return status;
1699 }
1700
1701 conf_port = resample_port;
1702 }
1703
Benny Prijono52a93912006-08-04 20:54:37 +00001704 /* Connect sound port to the bridge */
1705 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
1706 conf_port );
1707 if (status != PJ_SUCCESS) {
1708 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
1709 "sound device", status);
1710 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1711 pjsua_var.snd_port = NULL;
1712 return status;
1713 }
1714
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001715 /* Save the device IDs */
1716 pjsua_var.cap_dev = capture_dev;
1717 pjsua_var.play_dev = playback_dev;
1718
Benny Prijonoc53c6d72006-11-27 09:54:03 +00001719 /* Update sound device name. */
1720 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
1721 pjmedia_snd_stream_get_info(strm, &si);
1722 play_info = pjmedia_snd_get_dev_info(si.rec_id);
1723
1724 pjmedia_conf_set_port0_name(pjsua_var.mconf,
1725 pj_cstr(&tmp, play_info->name));
1726
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001727 return PJ_SUCCESS;
1728}
1729
1730
1731/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00001732 * Get currently active sound devices. If sound devices has not been created
1733 * (for example when pjsua_start() is not called), it is possible that
1734 * the function returns PJ_SUCCESS with -1 as device IDs.
1735 */
1736PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
1737 int *playback_dev)
1738{
1739 if (capture_dev) {
1740 *capture_dev = pjsua_var.cap_dev;
1741 }
1742 if (playback_dev) {
1743 *playback_dev = pjsua_var.play_dev;
1744 }
1745
1746 return PJ_SUCCESS;
1747}
1748
1749
1750/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001751 * Use null sound device.
1752 */
1753PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
1754{
1755 pjmedia_port *conf_port;
1756 pj_status_t status;
1757
1758 /* Close existing sound device */
1759 close_snd_dev();
1760
1761 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
1762
1763 /* Get the port0 of the conference bridge. */
1764 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1765 pj_assert(conf_port != NULL);
1766
1767 /* Create master port, connecting port0 of the conference bridge to
1768 * a null port.
1769 */
1770 status = pjmedia_master_port_create(pjsua_var.pool, pjsua_var.null_port,
1771 conf_port, 0, &pjsua_var.null_snd);
1772 if (status != PJ_SUCCESS) {
1773 pjsua_perror(THIS_FILE, "Unable to create null sound device",
1774 status);
1775 return status;
1776 }
1777
1778 /* Start the master port */
1779 status = pjmedia_master_port_start(pjsua_var.null_snd);
1780 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
1781
1782 return PJ_SUCCESS;
1783}
1784
1785
Benny Prijonoe909eac2006-07-27 22:04:56 +00001786
1787/*
1788 * Use no device!
1789 */
1790PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
1791{
1792 /* Close existing sound device */
1793 close_snd_dev();
1794
1795 pjsua_var.no_snd = PJ_TRUE;
1796 return pjmedia_conf_get_master_port(pjsua_var.mconf);
1797}
1798
1799
Benny Prijonof20687a2006-08-04 18:27:19 +00001800/*
1801 * Configure the AEC settings of the sound port.
1802 */
Benny Prijono5da50432006-08-07 10:24:52 +00001803PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00001804{
1805 pjsua_var.media_cfg.ec_tail_len = tail_ms;
1806
1807 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00001808 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
1809 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00001810
1811 return PJ_SUCCESS;
1812}
1813
1814
1815/*
1816 * Get current AEC tail length.
1817 */
Benny Prijono22dfe592006-08-06 12:07:13 +00001818PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00001819{
1820 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
1821 return PJ_SUCCESS;
1822}
1823
Benny Prijonoe909eac2006-07-27 22:04:56 +00001824
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001825/*****************************************************************************
1826 * Codecs.
1827 */
1828
1829/*
1830 * Enum all supported codecs in the system.
1831 */
1832PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
1833 unsigned *p_count )
1834{
1835 pjmedia_codec_mgr *codec_mgr;
1836 pjmedia_codec_info info[32];
1837 unsigned i, count, prio[32];
1838 pj_status_t status;
1839
1840 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1841 count = PJ_ARRAY_SIZE(info);
1842 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
1843 if (status != PJ_SUCCESS) {
1844 *p_count = 0;
1845 return status;
1846 }
1847
1848 if (count > *p_count) count = *p_count;
1849
1850 for (i=0; i<count; ++i) {
1851 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
1852 id[i].codec_id = pj_str(id[i].buf_);
1853 id[i].priority = (pj_uint8_t) prio[i];
1854 }
1855
1856 *p_count = count;
1857
1858 return PJ_SUCCESS;
1859}
1860
1861
1862/*
1863 * Change codec priority.
1864 */
1865PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
1866 pj_uint8_t priority )
1867{
1868 pjmedia_codec_mgr *codec_mgr;
1869
1870 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1871
1872 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
1873 priority);
1874}
1875
1876
1877/*
1878 * Get codec parameters.
1879 */
1880PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
1881 pjmedia_codec_param *param )
1882{
1883 const pjmedia_codec_info *info;
1884 pjmedia_codec_mgr *codec_mgr;
1885 unsigned count = 1;
1886 pj_status_t status;
1887
1888 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1889
1890 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
1891 &count, &info, NULL);
1892 if (status != PJ_SUCCESS)
1893 return status;
1894
1895 if (count != 1)
1896 return PJ_ENOTFOUND;
1897
1898 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
1899 return status;
1900}
1901
1902
1903/*
1904 * Set codec parameters.
1905 */
1906PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
1907 const pjmedia_codec_param *param)
1908{
Benny Prijono00cae612006-07-31 15:19:36 +00001909 PJ_UNUSED_ARG(id);
1910 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001911 PJ_TODO(set_codec_param);
1912 return PJ_SUCCESS;
1913}