blob: fabd08cac49cf22444689b7ac3cb70f3139b2620 [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
3 * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
4 *
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
25#define PTIME 10
26#define FPS (1000/PTIME)
27#define DEFAULT_RTP_PORT 4000
28
29
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{
39 pj_str_t codec_id;
Benny Prijono0498d902006-06-19 14:49:14 +000040 unsigned opt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000041 pj_status_t status;
42
43 /* Copy configuration */
44 pj_memcpy(&pjsua_var.media_cfg, cfg, sizeof(*cfg));
45
46 /* Normalize configuration */
Benny Prijonoeebe9af2006-06-13 22:57:13 +000047
48 if (pjsua_var.media_cfg.has_ioqueue &&
49 pjsua_var.media_cfg.thread_cnt == 0)
50 {
51 pjsua_var.media_cfg.thread_cnt = 1;
52 }
53
54 if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
55 pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
56 }
57
58 /* Create media endpoint. */
59 status = pjmedia_endpt_create(&pjsua_var.cp.factory,
60 pjsua_var.media_cfg.has_ioqueue? NULL :
61 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
62 pjsua_var.media_cfg.thread_cnt,
63 &pjsua_var.med_endpt);
64 if (status != PJ_SUCCESS) {
65 pjsua_perror(THIS_FILE,
66 "Media stack initialization has returned error",
67 status);
68 return status;
69 }
70
71 /* Register all codecs */
72#if PJMEDIA_HAS_SPEEX_CODEC
73 /* Register speex. */
74 status = pjmedia_codec_speex_init(pjsua_var.med_endpt,
Benny Prijono7ca96da2006-08-07 12:11:40 +000075 0,
Benny Prijono0498d902006-06-19 14:49:14 +000076 pjsua_var.media_cfg.quality,
77 pjsua_var.media_cfg.quality);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000078 if (status != PJ_SUCCESS) {
79 pjsua_perror(THIS_FILE, "Error initializing Speex codec",
80 status);
81 return status;
82 }
Benny Prijono7ca96da2006-08-07 12:11:40 +000083
84 /* Set speex/16000 to higher priority*/
85 codec_id = pj_str("speex/16000");
86 pjmedia_codec_mgr_set_codec_priority(
87 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
88 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
89
90 /* Set speex/8000 to next higher priority*/
91 codec_id = pj_str("speex/8000");
92 pjmedia_codec_mgr_set_codec_priority(
93 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
94 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
95
96
97
Benny Prijonoeebe9af2006-06-13 22:57:13 +000098#endif /* PJMEDIA_HAS_SPEEX_CODEC */
99
Benny Prijono00cae612006-07-31 15:19:36 +0000100#if PJMEDIA_HAS_ILBC_CODEC
101 /* Register iLBC. */
102 status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt,
103 pjsua_var.media_cfg.ilbc_mode);
104 if (status != PJ_SUCCESS) {
105 pjsua_perror(THIS_FILE, "Error initializing iLBC codec",
106 status);
107 return status;
108 }
109#endif /* PJMEDIA_HAS_ILBC_CODEC */
110
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000111#if PJMEDIA_HAS_GSM_CODEC
112 /* Register GSM */
113 status = pjmedia_codec_gsm_init(pjsua_var.med_endpt);
114 if (status != PJ_SUCCESS) {
115 pjsua_perror(THIS_FILE, "Error initializing GSM codec",
116 status);
117 return status;
118 }
119#endif /* PJMEDIA_HAS_GSM_CODEC */
120
121#if PJMEDIA_HAS_G711_CODEC
122 /* Register PCMA and PCMU */
123 status = pjmedia_codec_g711_init(pjsua_var.med_endpt);
124 if (status != PJ_SUCCESS) {
125 pjsua_perror(THIS_FILE, "Error initializing G711 codec",
126 status);
127 return status;
128 }
129#endif /* PJMEDIA_HAS_G711_CODEC */
130
131#if PJMEDIA_HAS_L16_CODEC
132 /* Register L16 family codecs, but disable all */
133 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
134 if (status != PJ_SUCCESS) {
135 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
136 status);
137 return status;
138 }
139
140 /* Disable ALL L16 codecs */
141 codec_id = pj_str("L16");
142 pjmedia_codec_mgr_set_codec_priority(
143 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
144 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
145
146#endif /* PJMEDIA_HAS_L16_CODEC */
147
148
149 /* Save additional conference bridge parameters for future
150 * reference.
151 */
152 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
153 PTIME / 1000;
154 pjsua_var.mconf_cfg.channel_count = 1;
155 pjsua_var.mconf_cfg.bits_per_sample = 16;
156
Benny Prijono0498d902006-06-19 14:49:14 +0000157 /* Init options for conference bridge. */
158 opt = PJMEDIA_CONF_NO_DEVICE;
159 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000160 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000161 {
162 opt |= PJMEDIA_CONF_SMALL_FILTER;
163 }
164 else if (pjsua_var.media_cfg.quality < 3) {
165 opt |= PJMEDIA_CONF_USE_LINEAR;
166 }
167
168
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000169 /* Init conference bridge. */
170 status = pjmedia_conf_create(pjsua_var.pool,
171 pjsua_var.media_cfg.max_media_ports,
172 pjsua_var.media_cfg.clock_rate,
173 pjsua_var.mconf_cfg.channel_count,
174 pjsua_var.mconf_cfg.samples_per_frame,
175 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000176 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000177 if (status != PJ_SUCCESS) {
178 pjsua_perror(THIS_FILE,
179 "Media stack initialization has returned error",
180 status);
181 return status;
182 }
183
184 /* Create null port just in case user wants to use null sound. */
185 status = pjmedia_null_port_create(pjsua_var.pool,
186 pjsua_var.media_cfg.clock_rate,
187 pjsua_var.mconf_cfg.channel_count,
188 pjsua_var.mconf_cfg.samples_per_frame,
189 pjsua_var.mconf_cfg.bits_per_sample,
190 &pjsua_var.null_port);
191 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
192
193 return PJ_SUCCESS;
194}
195
196
197/*
198 * Create RTP and RTCP socket pair, and possibly resolve their public
199 * address via STUN.
200 */
201static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
202 pjmedia_sock_info *skinfo)
203{
204 enum {
205 RTP_RETRY = 100
206 };
207 int i;
208 static pj_uint16_t rtp_port;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000209 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000210 pj_sockaddr_in mapped_addr[2];
211 pj_status_t status = PJ_SUCCESS;
212 pj_sock_t sock[2];
213
214 if (rtp_port == 0)
215 rtp_port = (pj_uint16_t)cfg->port;
216
217 for (i=0; i<2; ++i)
218 sock[i] = PJ_INVALID_SOCKET;
219
Benny Prijono0a5cad82006-09-26 13:21:02 +0000220 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
221 if (cfg->bound_addr.slen) {
222 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
223 if (status != PJ_SUCCESS) {
224 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
225 status);
226 return status;
227 }
228 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000229
230 /* Loop retry to bind RTP and RTCP sockets. */
231 for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) {
232
233 /* Create and bind RTP socket. */
234 status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[0]);
235 if (status != PJ_SUCCESS) {
236 pjsua_perror(THIS_FILE, "socket() error", status);
237 return status;
238 }
239
Benny Prijono0a5cad82006-09-26 13:21:02 +0000240 status = pj_sock_bind_in(sock[0], bound_addr.sin_addr.s_addr,
241 rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000242 if (status != PJ_SUCCESS) {
243 pj_sock_close(sock[0]);
244 sock[0] = PJ_INVALID_SOCKET;
245 continue;
246 }
247
248 /* Create and bind RTCP socket. */
249 status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[1]);
250 if (status != PJ_SUCCESS) {
251 pjsua_perror(THIS_FILE, "socket() error", status);
252 pj_sock_close(sock[0]);
253 return status;
254 }
255
Benny Prijono0a5cad82006-09-26 13:21:02 +0000256 status = pj_sock_bind_in(sock[1], bound_addr.sin_addr.s_addr,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000257 (pj_uint16_t)(rtp_port+1));
258 if (status != PJ_SUCCESS) {
259 pj_sock_close(sock[0]);
260 sock[0] = PJ_INVALID_SOCKET;
261
262 pj_sock_close(sock[1]);
263 sock[1] = PJ_INVALID_SOCKET;
264 continue;
265 }
266
267 /*
268 * If we're configured to use STUN, then find out the mapped address,
269 * and make sure that the mapped RTCP port is adjacent with the RTP.
270 */
271 if (cfg->stun_config.stun_srv1.slen) {
272 status=pj_stun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
273 &cfg->stun_config.stun_srv1,
274 cfg->stun_config.stun_port1,
275 &cfg->stun_config.stun_srv2,
276 cfg->stun_config.stun_port2,
277 mapped_addr);
278 if (status != PJ_SUCCESS) {
279 pjsua_perror(THIS_FILE, "STUN resolve error", status);
280 goto on_error;
281 }
282
283 if (pj_ntohs(mapped_addr[1].sin_port) ==
284 pj_ntohs(mapped_addr[0].sin_port)+1)
285 {
286 /* Success! */
287 break;
288 }
289
290 pj_sock_close(sock[0]);
291 sock[0] = PJ_INVALID_SOCKET;
292
293 pj_sock_close(sock[1]);
294 sock[1] = PJ_INVALID_SOCKET;
295
Benny Prijono0a5cad82006-09-26 13:21:02 +0000296 } else if (cfg->public_addr.slen) {
297
298 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
299 (pj_uint16_t)rtp_port);
300 if (status != PJ_SUCCESS)
301 goto on_error;
302
303 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
304 (pj_uint16_t)(rtp_port+1));
305 if (status != PJ_SUCCESS)
306 goto on_error;
307
308 break;
309
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000310 } else {
Benny Prijono594e4c52006-09-14 18:51:01 +0000311 pj_in_addr addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000312
313 /* Get local IP address. */
Benny Prijono594e4c52006-09-14 18:51:01 +0000314 status = pj_gethostip(&addr);
315 if (status != PJ_SUCCESS)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000316 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000317
318 for (i=0; i<2; ++i)
Benny Prijono594e4c52006-09-14 18:51:01 +0000319 mapped_addr[i].sin_addr = addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000320
321 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)rtp_port);
322 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(rtp_port+1));
323 break;
324 }
325 }
326
327 if (sock[0] == PJ_INVALID_SOCKET) {
328 PJ_LOG(1,(THIS_FILE,
329 "Unable to find appropriate RTP/RTCP ports combination"));
330 goto on_error;
331 }
332
333
334 skinfo->rtp_sock = sock[0];
335 pj_memcpy(&skinfo->rtp_addr_name,
336 &mapped_addr[0], sizeof(pj_sockaddr_in));
337
338 skinfo->rtcp_sock = sock[1];
339 pj_memcpy(&skinfo->rtcp_addr_name,
340 &mapped_addr[1], sizeof(pj_sockaddr_in));
341
342 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d",
343 pj_inet_ntoa(skinfo->rtp_addr_name.sin_addr),
344 pj_ntohs(skinfo->rtp_addr_name.sin_port)));
345 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s:%d",
346 pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr),
347 pj_ntohs(skinfo->rtcp_addr_name.sin_port)));
348
349 rtp_port += 2;
350 return PJ_SUCCESS;
351
352on_error:
353 for (i=0; i<2; ++i) {
354 if (sock[i] != PJ_INVALID_SOCKET)
355 pj_sock_close(sock[i]);
356 }
357 return status;
358}
359
360
361/*
362 * Start pjsua media subsystem.
363 */
364pj_status_t pjsua_media_subsys_start(void)
365{
366 pj_status_t status;
367
368 /* Create media for calls, if none is specified */
369 if (pjsua_var.calls[0].med_tp == NULL) {
370 pjsua_transport_config transport_cfg;
371
372 /* Create default transport config */
373 pjsua_transport_config_default(&transport_cfg);
374 transport_cfg.port = DEFAULT_RTP_PORT;
375
376 status = pjsua_media_transports_create(&transport_cfg);
377 if (status != PJ_SUCCESS)
378 return status;
379 }
380
381 /* Create sound port if none is created yet */
Benny Prijonoe909eac2006-07-27 22:04:56 +0000382 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
383 !pjsua_var.no_snd)
384 {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000385 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
386 if (status != PJ_SUCCESS) {
387 /* Error opening sound device, use null device */
388 char errmsg[PJ_ERR_MSG_SIZE];
389
390 pj_strerror(status, errmsg, sizeof(errmsg));
391 PJ_LOG(4,(THIS_FILE,
392 "Error opening default sound device (%s (status=%d)). "
393 "Will use NULL device instead",
394 errmsg, status));
395
396 status = pjsua_set_null_snd_dev();
397 if (status != PJ_SUCCESS) {
398 pjsua_perror(THIS_FILE, "Error opening NULL sound device",
399 status);
400 return status;
401 }
402 }
403 }
404
405 return PJ_SUCCESS;
406}
407
408
409/*
410 * Destroy pjsua media subsystem.
411 */
412pj_status_t pjsua_media_subsys_destroy(void)
413{
414 unsigned i;
415
416 close_snd_dev();
417
418 if (pjsua_var.mconf) {
419 pjmedia_conf_destroy(pjsua_var.mconf);
420 pjsua_var.mconf = NULL;
421 }
422
423 if (pjsua_var.null_port) {
424 pjmedia_port_destroy(pjsua_var.null_port);
425 pjsua_var.null_port = NULL;
426 }
427
428 /* Destroy file players */
429 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
430 if (pjsua_var.player[i].port) {
431 pjmedia_port_destroy(pjsua_var.player[i].port);
432 pjsua_var.player[i].port = NULL;
433 }
434 }
435
436 /* Destroy file recorders */
437 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
438 if (pjsua_var.recorder[i].port) {
439 pjmedia_port_destroy(pjsua_var.recorder[i].port);
440 pjsua_var.recorder[i].port = NULL;
441 }
442 }
443
444 /* Close media transports */
445 for (i=0; i<(int)pjsua_var.ua_cfg.max_calls; ++i) {
446 if (pjsua_var.calls[i].med_tp) {
447 (*pjsua_var.calls[i].med_tp->op->destroy)(pjsua_var.calls[i].med_tp);
448 pjsua_var.calls[i].med_tp = NULL;
449 }
450 }
451
452 /* Destroy media endpoint. */
453 if (pjsua_var.med_endpt) {
454
455 /* Shutdown all codecs: */
456# if PJMEDIA_HAS_SPEEX_CODEC
457 pjmedia_codec_speex_deinit();
458# endif /* PJMEDIA_HAS_SPEEX_CODEC */
459
460# if PJMEDIA_HAS_GSM_CODEC
461 pjmedia_codec_gsm_deinit();
462# endif /* PJMEDIA_HAS_GSM_CODEC */
463
464# if PJMEDIA_HAS_G711_CODEC
465 pjmedia_codec_g711_deinit();
466# endif /* PJMEDIA_HAS_G711_CODEC */
467
468# if PJMEDIA_HAS_L16_CODEC
469 pjmedia_codec_l16_deinit();
470# endif /* PJMEDIA_HAS_L16_CODEC */
471
472
473 pjmedia_endpt_destroy(pjsua_var.med_endpt);
474 pjsua_var.med_endpt = NULL;
475 }
476
477 return PJ_SUCCESS;
478}
479
480
481/*
482 * Create UDP media transports for all the calls. This function creates
483 * one UDP media transport for each call.
484 */
485PJ_DEF(pj_status_t)
486pjsua_media_transports_create(const pjsua_transport_config *app_cfg)
487{
488 pjsua_transport_config cfg;
489 unsigned i;
490 pj_status_t status;
491
492
493 /* Make sure pjsua_init() has been called */
494 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
495
496 PJSUA_LOCK();
497
498 /* Delete existing media transports */
499 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
500 if (pjsua_var.calls[i].med_tp != NULL) {
501 pjsua_var.calls[i].med_tp->op->destroy(pjsua_var.calls[i].med_tp);
502 pjsua_var.calls[i].med_tp = NULL;
503 }
504 }
505
506 /* Copy config */
507 pj_memcpy(&cfg, app_cfg, sizeof(*app_cfg));
508 pjsua_normalize_stun_config(&cfg.stun_config);
509
510 /* Create each media transport */
511 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
512
513 status = create_rtp_rtcp_sock(&cfg, &pjsua_var.calls[i].skinfo);
514 if (status != PJ_SUCCESS) {
515 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
516 status);
517 goto on_error;
518 }
519 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
520 &pjsua_var.calls[i].skinfo, 0,
521 &pjsua_var.calls[i].med_tp);
522 if (status != PJ_SUCCESS) {
523 pjsua_perror(THIS_FILE, "Unable to create media transport",
524 status);
525 goto on_error;
526 }
Benny Prijono00cae612006-07-31 15:19:36 +0000527
528 pjmedia_transport_udp_simulate_lost(pjsua_var.calls[i].med_tp,
529 PJMEDIA_DIR_ENCODING,
530 pjsua_var.media_cfg.tx_drop_pct);
531
532 pjmedia_transport_udp_simulate_lost(pjsua_var.calls[i].med_tp,
533 PJMEDIA_DIR_DECODING,
534 pjsua_var.media_cfg.rx_drop_pct);
535
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000536 }
537
538 PJSUA_UNLOCK();
539
540 return PJ_SUCCESS;
541
542on_error:
543 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
544 if (pjsua_var.calls[i].med_tp != NULL) {
545 pjsua_var.calls[i].med_tp->op->destroy(pjsua_var.calls[i].med_tp);
546 pjsua_var.calls[i].med_tp = NULL;
547 }
548 }
549
550 PJSUA_UNLOCK();
551
552 return status;
553}
554
555
556/*
557 * Get maxinum number of conference ports.
558 */
559PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
560{
561 return pjsua_var.media_cfg.max_media_ports;
562}
563
564
565/*
566 * Get current number of active ports in the bridge.
567 */
568PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
569{
570 unsigned ports[256];
571 unsigned count = PJ_ARRAY_SIZE(ports);
572 pj_status_t status;
573
574 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
575 if (status != PJ_SUCCESS)
576 count = 0;
577
578 return count;
579}
580
581
582/*
583 * Enumerate all conference ports.
584 */
585PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
586 unsigned *count)
587{
588 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
589}
590
591
592/*
593 * Get information about the specified conference port
594 */
595PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
596 pjsua_conf_port_info *info)
597{
598 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +0000599 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000600 pj_status_t status;
601
602 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
603 if (status != PJ_SUCCESS)
604 return status;
605
Benny Prijonoac623b32006-07-03 15:19:31 +0000606 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000607 info->slot_id = id;
608 info->name = cinfo.name;
609 info->clock_rate = cinfo.clock_rate;
610 info->channel_count = cinfo.channel_count;
611 info->samples_per_frame = cinfo.samples_per_frame;
612 info->bits_per_sample = cinfo.bits_per_sample;
613
614 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +0000615 info->listener_cnt = cinfo.listener_cnt;
616 for (i=0; i<cinfo.listener_cnt; ++i) {
617 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000618 }
619
620 return PJ_SUCCESS;
621}
622
623
624/*
Benny Prijonoe909eac2006-07-27 22:04:56 +0000625 * Add arbitrary media port to PJSUA's conference bridge.
626 */
627PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
628 pjmedia_port *port,
629 pjsua_conf_port_id *p_id)
630{
631 pj_status_t status;
632
633 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
634 port, NULL, (unsigned*)p_id);
635 if (status != PJ_SUCCESS) {
636 if (p_id)
637 *p_id = PJSUA_INVALID_ID;
638 }
639
640 return status;
641}
642
643
644/*
645 * Remove arbitrary slot from the conference bridge.
646 */
647PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
648{
649 return pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
650}
651
652
653/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000654 * Establish unidirectional media flow from souce to sink.
655 */
656PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
657 pjsua_conf_port_id sink)
658{
659 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
660}
661
662
663/*
664 * Disconnect media flow from the source to destination port.
665 */
666PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
667 pjsua_conf_port_id sink)
668{
669 return pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
670}
671
672
673/*****************************************************************************
674 * File player.
675 */
676
677/*
678 * Create a file player, and automatically connect this player to
679 * the conference bridge.
680 */
681PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
682 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000683 pjsua_player_id *p_id)
684{
685 unsigned slot, file_id;
686 char path[128];
687 pjmedia_port *port;
688 pj_status_t status;
689
690 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
691 return PJ_ETOOMANY;
692
693 PJSUA_LOCK();
694
695 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
696 if (pjsua_var.player[file_id].port == NULL)
697 break;
698 }
699
700 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
701 /* This is unexpected */
702 PJSUA_UNLOCK();
703 pj_assert(0);
704 return PJ_EBUG;
705 }
706
707 pj_memcpy(path, filename->ptr, filename->slen);
708 path[filename->slen] = '\0';
709 status = pjmedia_wav_player_port_create(pjsua_var.pool, path,
710 pjsua_var.mconf_cfg.samples_per_frame *
711 1000 / pjsua_var.media_cfg.clock_rate,
Benny Prijono00cae612006-07-31 15:19:36 +0000712 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000713 if (status != PJ_SUCCESS) {
714 PJSUA_UNLOCK();
715 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
716 return status;
717 }
718
719 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
720 port, filename, &slot);
721 if (status != PJ_SUCCESS) {
722 pjmedia_port_destroy(port);
723 PJSUA_UNLOCK();
724 return status;
725 }
726
727 pjsua_var.player[file_id].port = port;
728 pjsua_var.player[file_id].slot = slot;
729
730 if (p_id) *p_id = file_id;
731
732 ++pjsua_var.player_cnt;
733
734 PJSUA_UNLOCK();
735 return PJ_SUCCESS;
736}
737
738
739/*
740 * Get conference port ID associated with player.
741 */
742PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
743{
744 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
745 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
746
747 return pjsua_var.player[id].slot;
748}
749
750
751/*
752 * Set playback position.
753 */
754PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
755 pj_uint32_t samples)
756{
757 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
758 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
759
760 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
761}
762
763
764/*
765 * Close the file, remove the player from the bridge, and free
766 * resources associated with the file player.
767 */
768PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
769{
770 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
771 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
772
773 PJSUA_LOCK();
774
775 if (pjsua_var.player[id].port) {
776 pjmedia_conf_remove_port(pjsua_var.mconf,
777 pjsua_var.player[id].slot);
778 pjmedia_port_destroy(pjsua_var.player[id].port);
779 pjsua_var.player[id].port = NULL;
780 pjsua_var.player[id].slot = 0xFFFF;
781 pjsua_var.player_cnt--;
782 }
783
784 PJSUA_UNLOCK();
785
786 return PJ_SUCCESS;
787}
788
789
790/*****************************************************************************
791 * File recorder.
792 */
793
794/*
795 * Create a file recorder, and automatically connect this recorder to
796 * the conference bridge.
797 */
798PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
799 unsigned file_format,
800 const pj_str_t *encoding,
801 pj_ssize_t max_size,
802 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000803 pjsua_recorder_id *p_id)
804{
805 unsigned slot, file_id;
806 char path[128];
807 pjmedia_port *port;
808 pj_status_t status;
809
Benny Prijono00cae612006-07-31 15:19:36 +0000810 /* Don't support max_size at present */
811 PJ_ASSERT_RETURN(max_size == 0, PJ_EINVAL);
812
813 /* Don't support file format at present */
814 PJ_ASSERT_RETURN(file_format == 0, PJ_EINVAL);
815
816 /* Don't support encoding at present */
817 PJ_ASSERT_RETURN(encoding == NULL, PJ_EINVAL);
818
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000819 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
820 return PJ_ETOOMANY;
821
822 PJSUA_LOCK();
823
824 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
825 if (pjsua_var.recorder[file_id].port == NULL)
826 break;
827 }
828
829 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
830 /* This is unexpected */
831 PJSUA_UNLOCK();
832 pj_assert(0);
833 return PJ_EBUG;
834 }
835
836 pj_memcpy(path, filename->ptr, filename->slen);
837 path[filename->slen] = '\0';
838 status = pjmedia_wav_writer_port_create(pjsua_var.pool, path,
839 pjsua_var.media_cfg.clock_rate,
840 pjsua_var.mconf_cfg.channel_count,
841 pjsua_var.mconf_cfg.samples_per_frame,
842 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono00cae612006-07-31 15:19:36 +0000843 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000844 if (status != PJ_SUCCESS) {
845 PJSUA_UNLOCK();
846 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
847 return status;
848 }
849
850 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
851 port, filename, &slot);
852 if (status != PJ_SUCCESS) {
853 pjmedia_port_destroy(port);
854 PJSUA_UNLOCK();
855 return status;
856 }
857
858 pjsua_var.recorder[file_id].port = port;
859 pjsua_var.recorder[file_id].slot = slot;
860
861 if (p_id) *p_id = file_id;
862
863 ++pjsua_var.rec_cnt;
864
865 PJSUA_UNLOCK();
866 return PJ_SUCCESS;
867}
868
869
870/*
871 * Get conference port associated with recorder.
872 */
873PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
874{
875 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL);
876 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
877
878 return pjsua_var.recorder[id].slot;
879}
880
881
882/*
883 * Destroy recorder (this will complete recording).
884 */
885PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
886{
887 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL);
888 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
889
890 PJSUA_LOCK();
891
892 if (pjsua_var.recorder[id].port) {
893 pjmedia_conf_remove_port(pjsua_var.mconf,
894 pjsua_var.recorder[id].slot);
895 pjmedia_port_destroy(pjsua_var.recorder[id].port);
896 pjsua_var.recorder[id].port = NULL;
897 pjsua_var.recorder[id].slot = 0xFFFF;
898 pjsua_var.rec_cnt--;
899 }
900
901 PJSUA_UNLOCK();
902
903 return PJ_SUCCESS;
904}
905
906
907/*****************************************************************************
908 * Sound devices.
909 */
910
911/*
912 * Enum sound devices.
913 */
914PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
915 unsigned *count)
916{
917 unsigned i, dev_count;
918
919 dev_count = pjmedia_snd_get_dev_count();
920
921 if (dev_count > *count) dev_count = *count;
922
923 for (i=0; i<dev_count; ++i) {
924 const pjmedia_snd_dev_info *ci;
925
926 ci = pjmedia_snd_get_dev_info(i);
927 pj_memcpy(&info[i], ci, sizeof(*ci));
928 }
929
930 *count = dev_count;
931
932 return PJ_SUCCESS;
933}
934
935
936/* Close existing sound device */
937static void close_snd_dev(void)
938{
939 /* Close sound device */
940 if (pjsua_var.snd_port) {
941 const pjmedia_snd_dev_info *cap_info, *play_info;
942
943 cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
944 play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
945
946 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
947 "%s sound capture device",
948 play_info->name, cap_info->name));
949
950 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
951 pjmedia_snd_port_destroy(pjsua_var.snd_port);
952 pjsua_var.snd_port = NULL;
953 }
954
955 /* Close null sound device */
956 if (pjsua_var.null_snd) {
957 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
958 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
959 pjsua_var.null_snd = NULL;
960 }
961}
962
963/*
964 * Select or change sound device. Application may call this function at
965 * any time to replace current sound device.
966 */
967PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
968 int playback_dev)
969{
970 pjmedia_port *conf_port;
971 const pjmedia_snd_dev_info *cap_info, *play_info;
Benny Prijono26056d82006-10-11 16:03:41 +0000972 unsigned clock_rates[] = { 0, 22050, 44100, 48000, 11025, 32000, 8000};
973 unsigned i;
974 pj_status_t status = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000975
976 /* Close existing sound port */
977 close_snd_dev();
978
979
980 cap_info = pjmedia_snd_get_dev_info(capture_dev);
981 play_info = pjmedia_snd_get_dev_info(playback_dev);
982
Benny Prijono26056d82006-10-11 16:03:41 +0000983 /* Set default clock rate */
984 clock_rates[0] = pjsua_var.media_cfg.clock_rate;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000985
Benny Prijono26056d82006-10-11 16:03:41 +0000986 /* Attempts to open the sound device with different clock rates */
987 for (i=0; i<PJ_ARRAY_SIZE(clock_rates); ++i) {
988 char errmsg[PJ_ERR_MSG_SIZE];
989
990 PJ_LOG(4,(THIS_FILE,
991 "pjsua_set_snd_dev(): attempting to open devices "
992 "@%d Hz", clock_rates[i]));
993
994 /* Create the sound device. Sound port will start immediately. */
995 status = pjmedia_snd_port_create(pjsua_var.pool, capture_dev,
996 playback_dev,
997 clock_rates[i], 1,
998 clock_rates[i]/FPS,
999 16, 0, &pjsua_var.snd_port);
1000
1001 if (status == PJ_SUCCESS)
1002 break;
1003
1004 pj_strerror(status, errmsg, sizeof(errmsg));
1005 PJ_LOG(4, ("..failed: %s", errmsg));
1006 }
1007
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001008 if (status != PJ_SUCCESS) {
1009 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
1010 return status;
1011 }
1012
1013 /* Get the port0 of the conference bridge. */
1014 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1015 pj_assert(conf_port != NULL);
1016
Benny Prijonof20687a2006-08-04 18:27:19 +00001017 /* Set AEC */
Benny Prijono5da50432006-08-07 10:24:52 +00001018 pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
1019 pjsua_var.media_cfg.ec_tail_len,
1020 pjsua_var.media_cfg.ec_options);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001021
Benny Prijono52a93912006-08-04 20:54:37 +00001022 /* Connect sound port to the bridge */
1023 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
1024 conf_port );
1025 if (status != PJ_SUCCESS) {
1026 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
1027 "sound device", status);
1028 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1029 pjsua_var.snd_port = NULL;
1030 return status;
1031 }
1032
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001033 /* Save the device IDs */
1034 pjsua_var.cap_dev = capture_dev;
1035 pjsua_var.play_dev = playback_dev;
1036
1037 return PJ_SUCCESS;
1038}
1039
1040
1041/*
1042 * Use null sound device.
1043 */
1044PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
1045{
1046 pjmedia_port *conf_port;
1047 pj_status_t status;
1048
1049 /* Close existing sound device */
1050 close_snd_dev();
1051
1052 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
1053
1054 /* Get the port0 of the conference bridge. */
1055 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1056 pj_assert(conf_port != NULL);
1057
1058 /* Create master port, connecting port0 of the conference bridge to
1059 * a null port.
1060 */
1061 status = pjmedia_master_port_create(pjsua_var.pool, pjsua_var.null_port,
1062 conf_port, 0, &pjsua_var.null_snd);
1063 if (status != PJ_SUCCESS) {
1064 pjsua_perror(THIS_FILE, "Unable to create null sound device",
1065 status);
1066 return status;
1067 }
1068
1069 /* Start the master port */
1070 status = pjmedia_master_port_start(pjsua_var.null_snd);
1071 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
1072
1073 return PJ_SUCCESS;
1074}
1075
1076
Benny Prijonoe909eac2006-07-27 22:04:56 +00001077
1078/*
1079 * Use no device!
1080 */
1081PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
1082{
1083 /* Close existing sound device */
1084 close_snd_dev();
1085
1086 pjsua_var.no_snd = PJ_TRUE;
1087 return pjmedia_conf_get_master_port(pjsua_var.mconf);
1088}
1089
1090
Benny Prijonof20687a2006-08-04 18:27:19 +00001091/*
1092 * Configure the AEC settings of the sound port.
1093 */
Benny Prijono5da50432006-08-07 10:24:52 +00001094PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00001095{
1096 pjsua_var.media_cfg.ec_tail_len = tail_ms;
1097
1098 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00001099 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
1100 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00001101
1102 return PJ_SUCCESS;
1103}
1104
1105
1106/*
1107 * Get current AEC tail length.
1108 */
Benny Prijono22dfe592006-08-06 12:07:13 +00001109PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00001110{
1111 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
1112 return PJ_SUCCESS;
1113}
1114
Benny Prijonoe909eac2006-07-27 22:04:56 +00001115
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001116/*****************************************************************************
1117 * Codecs.
1118 */
1119
1120/*
1121 * Enum all supported codecs in the system.
1122 */
1123PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
1124 unsigned *p_count )
1125{
1126 pjmedia_codec_mgr *codec_mgr;
1127 pjmedia_codec_info info[32];
1128 unsigned i, count, prio[32];
1129 pj_status_t status;
1130
1131 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1132 count = PJ_ARRAY_SIZE(info);
1133 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
1134 if (status != PJ_SUCCESS) {
1135 *p_count = 0;
1136 return status;
1137 }
1138
1139 if (count > *p_count) count = *p_count;
1140
1141 for (i=0; i<count; ++i) {
1142 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
1143 id[i].codec_id = pj_str(id[i].buf_);
1144 id[i].priority = (pj_uint8_t) prio[i];
1145 }
1146
1147 *p_count = count;
1148
1149 return PJ_SUCCESS;
1150}
1151
1152
1153/*
1154 * Change codec priority.
1155 */
1156PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
1157 pj_uint8_t priority )
1158{
1159 pjmedia_codec_mgr *codec_mgr;
1160
1161 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1162
1163 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
1164 priority);
1165}
1166
1167
1168/*
1169 * Get codec parameters.
1170 */
1171PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
1172 pjmedia_codec_param *param )
1173{
1174 const pjmedia_codec_info *info;
1175 pjmedia_codec_mgr *codec_mgr;
1176 unsigned count = 1;
1177 pj_status_t status;
1178
1179 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1180
1181 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
1182 &count, &info, NULL);
1183 if (status != PJ_SUCCESS)
1184 return status;
1185
1186 if (count != 1)
1187 return PJ_ENOTFOUND;
1188
1189 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
1190 return status;
1191}
1192
1193
1194/*
1195 * Set codec parameters.
1196 */
1197PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
1198 const pjmedia_codec_param *param)
1199{
Benny Prijono00cae612006-07-31 15:19:36 +00001200 PJ_UNUSED_ARG(id);
1201 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001202 PJ_TODO(set_codec_param);
1203 return PJ_SUCCESS;
1204}