blob: d92d0cb94ba7515914747e5fd3a289c0b1c86673 [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 */
47#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
48 pjsua_var.media_cfg.clock_rate = 44100;
49#endif
50
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,
78 PJMEDIA_SPEEX_NO_UWB,
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 }
86#endif /* PJMEDIA_HAS_SPEEX_CODEC */
87
Benny Prijono00cae612006-07-31 15:19:36 +000088#if PJMEDIA_HAS_ILBC_CODEC
89 /* Register iLBC. */
90 status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt,
91 pjsua_var.media_cfg.ilbc_mode);
92 if (status != PJ_SUCCESS) {
93 pjsua_perror(THIS_FILE, "Error initializing iLBC codec",
94 status);
95 return status;
96 }
97#endif /* PJMEDIA_HAS_ILBC_CODEC */
98
Benny Prijonoeebe9af2006-06-13 22:57:13 +000099#if PJMEDIA_HAS_GSM_CODEC
100 /* Register GSM */
101 status = pjmedia_codec_gsm_init(pjsua_var.med_endpt);
102 if (status != PJ_SUCCESS) {
103 pjsua_perror(THIS_FILE, "Error initializing GSM codec",
104 status);
105 return status;
106 }
107#endif /* PJMEDIA_HAS_GSM_CODEC */
108
109#if PJMEDIA_HAS_G711_CODEC
110 /* Register PCMA and PCMU */
111 status = pjmedia_codec_g711_init(pjsua_var.med_endpt);
112 if (status != PJ_SUCCESS) {
113 pjsua_perror(THIS_FILE, "Error initializing G711 codec",
114 status);
115 return status;
116 }
117#endif /* PJMEDIA_HAS_G711_CODEC */
118
119#if PJMEDIA_HAS_L16_CODEC
120 /* Register L16 family codecs, but disable all */
121 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
122 if (status != PJ_SUCCESS) {
123 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
124 status);
125 return status;
126 }
127
128 /* Disable ALL L16 codecs */
129 codec_id = pj_str("L16");
130 pjmedia_codec_mgr_set_codec_priority(
131 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
132 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
133
134#endif /* PJMEDIA_HAS_L16_CODEC */
135
136
137 /* Save additional conference bridge parameters for future
138 * reference.
139 */
140 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
141 PTIME / 1000;
142 pjsua_var.mconf_cfg.channel_count = 1;
143 pjsua_var.mconf_cfg.bits_per_sample = 16;
144
Benny Prijono0498d902006-06-19 14:49:14 +0000145 /* Init options for conference bridge. */
146 opt = PJMEDIA_CONF_NO_DEVICE;
147 if (pjsua_var.media_cfg.quality >= 3 &&
148 pjsua_var.media_cfg.quality <= 7)
149 {
150 opt |= PJMEDIA_CONF_SMALL_FILTER;
151 }
152 else if (pjsua_var.media_cfg.quality < 3) {
153 opt |= PJMEDIA_CONF_USE_LINEAR;
154 }
155
156
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000157 /* Init conference bridge. */
158 status = pjmedia_conf_create(pjsua_var.pool,
159 pjsua_var.media_cfg.max_media_ports,
160 pjsua_var.media_cfg.clock_rate,
161 pjsua_var.mconf_cfg.channel_count,
162 pjsua_var.mconf_cfg.samples_per_frame,
163 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000164 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000165 if (status != PJ_SUCCESS) {
166 pjsua_perror(THIS_FILE,
167 "Media stack initialization has returned error",
168 status);
169 return status;
170 }
171
172 /* Create null port just in case user wants to use null sound. */
173 status = pjmedia_null_port_create(pjsua_var.pool,
174 pjsua_var.media_cfg.clock_rate,
175 pjsua_var.mconf_cfg.channel_count,
176 pjsua_var.mconf_cfg.samples_per_frame,
177 pjsua_var.mconf_cfg.bits_per_sample,
178 &pjsua_var.null_port);
179 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
180
181 return PJ_SUCCESS;
182}
183
184
185/*
186 * Create RTP and RTCP socket pair, and possibly resolve their public
187 * address via STUN.
188 */
189static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
190 pjmedia_sock_info *skinfo)
191{
192 enum {
193 RTP_RETRY = 100
194 };
195 int i;
196 static pj_uint16_t rtp_port;
197 pj_sockaddr_in mapped_addr[2];
198 pj_status_t status = PJ_SUCCESS;
199 pj_sock_t sock[2];
200
201 if (rtp_port == 0)
202 rtp_port = (pj_uint16_t)cfg->port;
203
204 for (i=0; i<2; ++i)
205 sock[i] = PJ_INVALID_SOCKET;
206
207
208 /* Loop retry to bind RTP and RTCP sockets. */
209 for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) {
210
211 /* Create and bind RTP socket. */
212 status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[0]);
213 if (status != PJ_SUCCESS) {
214 pjsua_perror(THIS_FILE, "socket() error", status);
215 return status;
216 }
217
218 status = pj_sock_bind_in(sock[0], cfg->ip_addr.s_addr, rtp_port);
219 if (status != PJ_SUCCESS) {
220 pj_sock_close(sock[0]);
221 sock[0] = PJ_INVALID_SOCKET;
222 continue;
223 }
224
225 /* Create and bind RTCP socket. */
226 status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[1]);
227 if (status != PJ_SUCCESS) {
228 pjsua_perror(THIS_FILE, "socket() error", status);
229 pj_sock_close(sock[0]);
230 return status;
231 }
232
233 status = pj_sock_bind_in(sock[1], cfg->ip_addr.s_addr,
234 (pj_uint16_t)(rtp_port+1));
235 if (status != PJ_SUCCESS) {
236 pj_sock_close(sock[0]);
237 sock[0] = PJ_INVALID_SOCKET;
238
239 pj_sock_close(sock[1]);
240 sock[1] = PJ_INVALID_SOCKET;
241 continue;
242 }
243
244 /*
245 * If we're configured to use STUN, then find out the mapped address,
246 * and make sure that the mapped RTCP port is adjacent with the RTP.
247 */
248 if (cfg->stun_config.stun_srv1.slen) {
249 status=pj_stun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
250 &cfg->stun_config.stun_srv1,
251 cfg->stun_config.stun_port1,
252 &cfg->stun_config.stun_srv2,
253 cfg->stun_config.stun_port2,
254 mapped_addr);
255 if (status != PJ_SUCCESS) {
256 pjsua_perror(THIS_FILE, "STUN resolve error", status);
257 goto on_error;
258 }
259
260 if (pj_ntohs(mapped_addr[1].sin_port) ==
261 pj_ntohs(mapped_addr[0].sin_port)+1)
262 {
263 /* Success! */
264 break;
265 }
266
267 pj_sock_close(sock[0]);
268 sock[0] = PJ_INVALID_SOCKET;
269
270 pj_sock_close(sock[1]);
271 sock[1] = PJ_INVALID_SOCKET;
272
273 } else {
274 const pj_str_t *hostname;
275 pj_sockaddr_in addr;
276
277 /* Get local IP address. */
278 hostname = pj_gethostname();
279
Benny Prijonoac623b32006-07-03 15:19:31 +0000280 pj_bzero( &addr, sizeof(addr));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000281 addr.sin_family = PJ_AF_INET;
282 status = pj_sockaddr_in_set_str_addr( &addr, hostname);
283 if (status != PJ_SUCCESS) {
284 pjsua_perror(THIS_FILE, "Unresolvable local hostname",
285 status);
286 goto on_error;
287 }
288
289 for (i=0; i<2; ++i)
290 pj_memcpy(&mapped_addr[i], &addr, sizeof(addr));
291
292 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)rtp_port);
293 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(rtp_port+1));
294 break;
295 }
296 }
297
298 if (sock[0] == PJ_INVALID_SOCKET) {
299 PJ_LOG(1,(THIS_FILE,
300 "Unable to find appropriate RTP/RTCP ports combination"));
301 goto on_error;
302 }
303
304
305 skinfo->rtp_sock = sock[0];
306 pj_memcpy(&skinfo->rtp_addr_name,
307 &mapped_addr[0], sizeof(pj_sockaddr_in));
308
309 skinfo->rtcp_sock = sock[1];
310 pj_memcpy(&skinfo->rtcp_addr_name,
311 &mapped_addr[1], sizeof(pj_sockaddr_in));
312
313 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d",
314 pj_inet_ntoa(skinfo->rtp_addr_name.sin_addr),
315 pj_ntohs(skinfo->rtp_addr_name.sin_port)));
316 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s:%d",
317 pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr),
318 pj_ntohs(skinfo->rtcp_addr_name.sin_port)));
319
320 rtp_port += 2;
321 return PJ_SUCCESS;
322
323on_error:
324 for (i=0; i<2; ++i) {
325 if (sock[i] != PJ_INVALID_SOCKET)
326 pj_sock_close(sock[i]);
327 }
328 return status;
329}
330
331
332/*
333 * Start pjsua media subsystem.
334 */
335pj_status_t pjsua_media_subsys_start(void)
336{
337 pj_status_t status;
338
339 /* Create media for calls, if none is specified */
340 if (pjsua_var.calls[0].med_tp == NULL) {
341 pjsua_transport_config transport_cfg;
342
343 /* Create default transport config */
344 pjsua_transport_config_default(&transport_cfg);
345 transport_cfg.port = DEFAULT_RTP_PORT;
346
347 status = pjsua_media_transports_create(&transport_cfg);
348 if (status != PJ_SUCCESS)
349 return status;
350 }
351
352 /* Create sound port if none is created yet */
Benny Prijonoe909eac2006-07-27 22:04:56 +0000353 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
354 !pjsua_var.no_snd)
355 {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000356 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
357 if (status != PJ_SUCCESS) {
358 /* Error opening sound device, use null device */
359 char errmsg[PJ_ERR_MSG_SIZE];
360
361 pj_strerror(status, errmsg, sizeof(errmsg));
362 PJ_LOG(4,(THIS_FILE,
363 "Error opening default sound device (%s (status=%d)). "
364 "Will use NULL device instead",
365 errmsg, status));
366
367 status = pjsua_set_null_snd_dev();
368 if (status != PJ_SUCCESS) {
369 pjsua_perror(THIS_FILE, "Error opening NULL sound device",
370 status);
371 return status;
372 }
373 }
374 }
375
376 return PJ_SUCCESS;
377}
378
379
380/*
381 * Destroy pjsua media subsystem.
382 */
383pj_status_t pjsua_media_subsys_destroy(void)
384{
385 unsigned i;
386
387 close_snd_dev();
388
389 if (pjsua_var.mconf) {
390 pjmedia_conf_destroy(pjsua_var.mconf);
391 pjsua_var.mconf = NULL;
392 }
393
394 if (pjsua_var.null_port) {
395 pjmedia_port_destroy(pjsua_var.null_port);
396 pjsua_var.null_port = NULL;
397 }
398
399 /* Destroy file players */
400 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
401 if (pjsua_var.player[i].port) {
402 pjmedia_port_destroy(pjsua_var.player[i].port);
403 pjsua_var.player[i].port = NULL;
404 }
405 }
406
407 /* Destroy file recorders */
408 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
409 if (pjsua_var.recorder[i].port) {
410 pjmedia_port_destroy(pjsua_var.recorder[i].port);
411 pjsua_var.recorder[i].port = NULL;
412 }
413 }
414
415 /* Close media transports */
416 for (i=0; i<(int)pjsua_var.ua_cfg.max_calls; ++i) {
417 if (pjsua_var.calls[i].med_tp) {
418 (*pjsua_var.calls[i].med_tp->op->destroy)(pjsua_var.calls[i].med_tp);
419 pjsua_var.calls[i].med_tp = NULL;
420 }
421 }
422
423 /* Destroy media endpoint. */
424 if (pjsua_var.med_endpt) {
425
426 /* Shutdown all codecs: */
427# if PJMEDIA_HAS_SPEEX_CODEC
428 pjmedia_codec_speex_deinit();
429# endif /* PJMEDIA_HAS_SPEEX_CODEC */
430
431# if PJMEDIA_HAS_GSM_CODEC
432 pjmedia_codec_gsm_deinit();
433# endif /* PJMEDIA_HAS_GSM_CODEC */
434
435# if PJMEDIA_HAS_G711_CODEC
436 pjmedia_codec_g711_deinit();
437# endif /* PJMEDIA_HAS_G711_CODEC */
438
439# if PJMEDIA_HAS_L16_CODEC
440 pjmedia_codec_l16_deinit();
441# endif /* PJMEDIA_HAS_L16_CODEC */
442
443
444 pjmedia_endpt_destroy(pjsua_var.med_endpt);
445 pjsua_var.med_endpt = NULL;
446 }
447
448 return PJ_SUCCESS;
449}
450
451
452/*
453 * Create UDP media transports for all the calls. This function creates
454 * one UDP media transport for each call.
455 */
456PJ_DEF(pj_status_t)
457pjsua_media_transports_create(const pjsua_transport_config *app_cfg)
458{
459 pjsua_transport_config cfg;
460 unsigned i;
461 pj_status_t status;
462
463
464 /* Make sure pjsua_init() has been called */
465 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
466
467 PJSUA_LOCK();
468
469 /* Delete existing media transports */
470 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
471 if (pjsua_var.calls[i].med_tp != NULL) {
472 pjsua_var.calls[i].med_tp->op->destroy(pjsua_var.calls[i].med_tp);
473 pjsua_var.calls[i].med_tp = NULL;
474 }
475 }
476
477 /* Copy config */
478 pj_memcpy(&cfg, app_cfg, sizeof(*app_cfg));
479 pjsua_normalize_stun_config(&cfg.stun_config);
480
481 /* Create each media transport */
482 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
483
484 status = create_rtp_rtcp_sock(&cfg, &pjsua_var.calls[i].skinfo);
485 if (status != PJ_SUCCESS) {
486 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
487 status);
488 goto on_error;
489 }
490 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
491 &pjsua_var.calls[i].skinfo, 0,
492 &pjsua_var.calls[i].med_tp);
493 if (status != PJ_SUCCESS) {
494 pjsua_perror(THIS_FILE, "Unable to create media transport",
495 status);
496 goto on_error;
497 }
Benny Prijono00cae612006-07-31 15:19:36 +0000498
499 pjmedia_transport_udp_simulate_lost(pjsua_var.calls[i].med_tp,
500 PJMEDIA_DIR_ENCODING,
501 pjsua_var.media_cfg.tx_drop_pct);
502
503 pjmedia_transport_udp_simulate_lost(pjsua_var.calls[i].med_tp,
504 PJMEDIA_DIR_DECODING,
505 pjsua_var.media_cfg.rx_drop_pct);
506
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000507 }
508
509 PJSUA_UNLOCK();
510
511 return PJ_SUCCESS;
512
513on_error:
514 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
515 if (pjsua_var.calls[i].med_tp != NULL) {
516 pjsua_var.calls[i].med_tp->op->destroy(pjsua_var.calls[i].med_tp);
517 pjsua_var.calls[i].med_tp = NULL;
518 }
519 }
520
521 PJSUA_UNLOCK();
522
523 return status;
524}
525
526
527/*
528 * Get maxinum number of conference ports.
529 */
530PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
531{
532 return pjsua_var.media_cfg.max_media_ports;
533}
534
535
536/*
537 * Get current number of active ports in the bridge.
538 */
539PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
540{
541 unsigned ports[256];
542 unsigned count = PJ_ARRAY_SIZE(ports);
543 pj_status_t status;
544
545 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
546 if (status != PJ_SUCCESS)
547 count = 0;
548
549 return count;
550}
551
552
553/*
554 * Enumerate all conference ports.
555 */
556PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
557 unsigned *count)
558{
559 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
560}
561
562
563/*
564 * Get information about the specified conference port
565 */
566PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
567 pjsua_conf_port_info *info)
568{
569 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +0000570 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000571 pj_status_t status;
572
573 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
574 if (status != PJ_SUCCESS)
575 return status;
576
Benny Prijonoac623b32006-07-03 15:19:31 +0000577 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000578 info->slot_id = id;
579 info->name = cinfo.name;
580 info->clock_rate = cinfo.clock_rate;
581 info->channel_count = cinfo.channel_count;
582 info->samples_per_frame = cinfo.samples_per_frame;
583 info->bits_per_sample = cinfo.bits_per_sample;
584
585 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +0000586 info->listener_cnt = cinfo.listener_cnt;
587 for (i=0; i<cinfo.listener_cnt; ++i) {
588 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000589 }
590
591 return PJ_SUCCESS;
592}
593
594
595/*
Benny Prijonoe909eac2006-07-27 22:04:56 +0000596 * Add arbitrary media port to PJSUA's conference bridge.
597 */
598PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
599 pjmedia_port *port,
600 pjsua_conf_port_id *p_id)
601{
602 pj_status_t status;
603
604 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
605 port, NULL, (unsigned*)p_id);
606 if (status != PJ_SUCCESS) {
607 if (p_id)
608 *p_id = PJSUA_INVALID_ID;
609 }
610
611 return status;
612}
613
614
615/*
616 * Remove arbitrary slot from the conference bridge.
617 */
618PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
619{
620 return pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
621}
622
623
624/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000625 * Establish unidirectional media flow from souce to sink.
626 */
627PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
628 pjsua_conf_port_id sink)
629{
630 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
631}
632
633
634/*
635 * Disconnect media flow from the source to destination port.
636 */
637PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
638 pjsua_conf_port_id sink)
639{
640 return pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
641}
642
643
644/*****************************************************************************
645 * File player.
646 */
647
648/*
649 * Create a file player, and automatically connect this player to
650 * the conference bridge.
651 */
652PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
653 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000654 pjsua_player_id *p_id)
655{
656 unsigned slot, file_id;
657 char path[128];
658 pjmedia_port *port;
659 pj_status_t status;
660
661 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
662 return PJ_ETOOMANY;
663
664 PJSUA_LOCK();
665
666 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
667 if (pjsua_var.player[file_id].port == NULL)
668 break;
669 }
670
671 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
672 /* This is unexpected */
673 PJSUA_UNLOCK();
674 pj_assert(0);
675 return PJ_EBUG;
676 }
677
678 pj_memcpy(path, filename->ptr, filename->slen);
679 path[filename->slen] = '\0';
680 status = pjmedia_wav_player_port_create(pjsua_var.pool, path,
681 pjsua_var.mconf_cfg.samples_per_frame *
682 1000 / pjsua_var.media_cfg.clock_rate,
Benny Prijono00cae612006-07-31 15:19:36 +0000683 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000684 if (status != PJ_SUCCESS) {
685 PJSUA_UNLOCK();
686 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
687 return status;
688 }
689
690 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
691 port, filename, &slot);
692 if (status != PJ_SUCCESS) {
693 pjmedia_port_destroy(port);
694 PJSUA_UNLOCK();
695 return status;
696 }
697
698 pjsua_var.player[file_id].port = port;
699 pjsua_var.player[file_id].slot = slot;
700
701 if (p_id) *p_id = file_id;
702
703 ++pjsua_var.player_cnt;
704
705 PJSUA_UNLOCK();
706 return PJ_SUCCESS;
707}
708
709
710/*
711 * Get conference port ID associated with player.
712 */
713PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
714{
715 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
716 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
717
718 return pjsua_var.player[id].slot;
719}
720
721
722/*
723 * Set playback position.
724 */
725PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
726 pj_uint32_t samples)
727{
728 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
729 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
730
731 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
732}
733
734
735/*
736 * Close the file, remove the player from the bridge, and free
737 * resources associated with the file player.
738 */
739PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
740{
741 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
742 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
743
744 PJSUA_LOCK();
745
746 if (pjsua_var.player[id].port) {
747 pjmedia_conf_remove_port(pjsua_var.mconf,
748 pjsua_var.player[id].slot);
749 pjmedia_port_destroy(pjsua_var.player[id].port);
750 pjsua_var.player[id].port = NULL;
751 pjsua_var.player[id].slot = 0xFFFF;
752 pjsua_var.player_cnt--;
753 }
754
755 PJSUA_UNLOCK();
756
757 return PJ_SUCCESS;
758}
759
760
761/*****************************************************************************
762 * File recorder.
763 */
764
765/*
766 * Create a file recorder, and automatically connect this recorder to
767 * the conference bridge.
768 */
769PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
770 unsigned file_format,
771 const pj_str_t *encoding,
772 pj_ssize_t max_size,
773 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000774 pjsua_recorder_id *p_id)
775{
776 unsigned slot, file_id;
777 char path[128];
778 pjmedia_port *port;
779 pj_status_t status;
780
Benny Prijono00cae612006-07-31 15:19:36 +0000781 /* Don't support max_size at present */
782 PJ_ASSERT_RETURN(max_size == 0, PJ_EINVAL);
783
784 /* Don't support file format at present */
785 PJ_ASSERT_RETURN(file_format == 0, PJ_EINVAL);
786
787 /* Don't support encoding at present */
788 PJ_ASSERT_RETURN(encoding == NULL, PJ_EINVAL);
789
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000790 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
791 return PJ_ETOOMANY;
792
793 PJSUA_LOCK();
794
795 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
796 if (pjsua_var.recorder[file_id].port == NULL)
797 break;
798 }
799
800 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
801 /* This is unexpected */
802 PJSUA_UNLOCK();
803 pj_assert(0);
804 return PJ_EBUG;
805 }
806
807 pj_memcpy(path, filename->ptr, filename->slen);
808 path[filename->slen] = '\0';
809 status = pjmedia_wav_writer_port_create(pjsua_var.pool, path,
810 pjsua_var.media_cfg.clock_rate,
811 pjsua_var.mconf_cfg.channel_count,
812 pjsua_var.mconf_cfg.samples_per_frame,
813 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono00cae612006-07-31 15:19:36 +0000814 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000815 if (status != PJ_SUCCESS) {
816 PJSUA_UNLOCK();
817 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
818 return status;
819 }
820
821 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
822 port, filename, &slot);
823 if (status != PJ_SUCCESS) {
824 pjmedia_port_destroy(port);
825 PJSUA_UNLOCK();
826 return status;
827 }
828
829 pjsua_var.recorder[file_id].port = port;
830 pjsua_var.recorder[file_id].slot = slot;
831
832 if (p_id) *p_id = file_id;
833
834 ++pjsua_var.rec_cnt;
835
836 PJSUA_UNLOCK();
837 return PJ_SUCCESS;
838}
839
840
841/*
842 * Get conference port associated with recorder.
843 */
844PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
845{
846 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL);
847 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
848
849 return pjsua_var.recorder[id].slot;
850}
851
852
853/*
854 * Destroy recorder (this will complete recording).
855 */
856PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
857{
858 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL);
859 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
860
861 PJSUA_LOCK();
862
863 if (pjsua_var.recorder[id].port) {
864 pjmedia_conf_remove_port(pjsua_var.mconf,
865 pjsua_var.recorder[id].slot);
866 pjmedia_port_destroy(pjsua_var.recorder[id].port);
867 pjsua_var.recorder[id].port = NULL;
868 pjsua_var.recorder[id].slot = 0xFFFF;
869 pjsua_var.rec_cnt--;
870 }
871
872 PJSUA_UNLOCK();
873
874 return PJ_SUCCESS;
875}
876
877
878/*****************************************************************************
879 * Sound devices.
880 */
881
882/*
883 * Enum sound devices.
884 */
885PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
886 unsigned *count)
887{
888 unsigned i, dev_count;
889
890 dev_count = pjmedia_snd_get_dev_count();
891
892 if (dev_count > *count) dev_count = *count;
893
894 for (i=0; i<dev_count; ++i) {
895 const pjmedia_snd_dev_info *ci;
896
897 ci = pjmedia_snd_get_dev_info(i);
898 pj_memcpy(&info[i], ci, sizeof(*ci));
899 }
900
901 *count = dev_count;
902
903 return PJ_SUCCESS;
904}
905
906
907/* Close existing sound device */
908static void close_snd_dev(void)
909{
910 /* Close sound device */
911 if (pjsua_var.snd_port) {
912 const pjmedia_snd_dev_info *cap_info, *play_info;
913
914 cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
915 play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
916
917 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
918 "%s sound capture device",
919 play_info->name, cap_info->name));
920
921 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
922 pjmedia_snd_port_destroy(pjsua_var.snd_port);
923 pjsua_var.snd_port = NULL;
924 }
925
926 /* Close null sound device */
927 if (pjsua_var.null_snd) {
928 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
929 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
930 pjsua_var.null_snd = NULL;
931 }
932}
933
934/*
935 * Select or change sound device. Application may call this function at
936 * any time to replace current sound device.
937 */
938PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
939 int playback_dev)
940{
941 pjmedia_port *conf_port;
942 const pjmedia_snd_dev_info *cap_info, *play_info;
943 pj_status_t status;
944
945 /* Close existing sound port */
946 close_snd_dev();
947
948
949 cap_info = pjmedia_snd_get_dev_info(capture_dev);
950 play_info = pjmedia_snd_get_dev_info(playback_dev);
951
952 PJ_LOG(4,(THIS_FILE, "Opening %s sound playback device and "
953 "%s sound capture device..",
954 play_info->name, cap_info->name));
955
956 /* Create the sound device. Sound port will start immediately. */
957 status = pjmedia_snd_port_create(pjsua_var.pool, capture_dev,
958 playback_dev,
959 pjsua_var.media_cfg.clock_rate, 1,
960 pjsua_var.media_cfg.clock_rate/FPS,
961 16, 0, &pjsua_var.snd_port);
962 if (status != PJ_SUCCESS) {
963 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
964 return status;
965 }
966
967 /* Get the port0 of the conference bridge. */
968 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
969 pj_assert(conf_port != NULL);
970
971 /* Connect to the conference port */
972 status = pjmedia_snd_port_connect(pjsua_var.snd_port, conf_port);
973 if (status != PJ_SUCCESS) {
974 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
975 "sound device", status);
976 pjmedia_snd_port_destroy(pjsua_var.snd_port);
977 pjsua_var.snd_port = NULL;
978 return status;
979 }
980
981 /* Save the device IDs */
982 pjsua_var.cap_dev = capture_dev;
983 pjsua_var.play_dev = playback_dev;
984
985 return PJ_SUCCESS;
986}
987
988
989/*
990 * Use null sound device.
991 */
992PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
993{
994 pjmedia_port *conf_port;
995 pj_status_t status;
996
997 /* Close existing sound device */
998 close_snd_dev();
999
1000 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
1001
1002 /* Get the port0 of the conference bridge. */
1003 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1004 pj_assert(conf_port != NULL);
1005
1006 /* Create master port, connecting port0 of the conference bridge to
1007 * a null port.
1008 */
1009 status = pjmedia_master_port_create(pjsua_var.pool, pjsua_var.null_port,
1010 conf_port, 0, &pjsua_var.null_snd);
1011 if (status != PJ_SUCCESS) {
1012 pjsua_perror(THIS_FILE, "Unable to create null sound device",
1013 status);
1014 return status;
1015 }
1016
1017 /* Start the master port */
1018 status = pjmedia_master_port_start(pjsua_var.null_snd);
1019 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
1020
1021 return PJ_SUCCESS;
1022}
1023
1024
Benny Prijonoe909eac2006-07-27 22:04:56 +00001025
1026/*
1027 * Use no device!
1028 */
1029PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
1030{
1031 /* Close existing sound device */
1032 close_snd_dev();
1033
1034 pjsua_var.no_snd = PJ_TRUE;
1035 return pjmedia_conf_get_master_port(pjsua_var.mconf);
1036}
1037
1038
1039
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001040/*****************************************************************************
1041 * Codecs.
1042 */
1043
1044/*
1045 * Enum all supported codecs in the system.
1046 */
1047PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
1048 unsigned *p_count )
1049{
1050 pjmedia_codec_mgr *codec_mgr;
1051 pjmedia_codec_info info[32];
1052 unsigned i, count, prio[32];
1053 pj_status_t status;
1054
1055 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1056 count = PJ_ARRAY_SIZE(info);
1057 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
1058 if (status != PJ_SUCCESS) {
1059 *p_count = 0;
1060 return status;
1061 }
1062
1063 if (count > *p_count) count = *p_count;
1064
1065 for (i=0; i<count; ++i) {
1066 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
1067 id[i].codec_id = pj_str(id[i].buf_);
1068 id[i].priority = (pj_uint8_t) prio[i];
1069 }
1070
1071 *p_count = count;
1072
1073 return PJ_SUCCESS;
1074}
1075
1076
1077/*
1078 * Change codec priority.
1079 */
1080PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
1081 pj_uint8_t priority )
1082{
1083 pjmedia_codec_mgr *codec_mgr;
1084
1085 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1086
1087 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
1088 priority);
1089}
1090
1091
1092/*
1093 * Get codec parameters.
1094 */
1095PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
1096 pjmedia_codec_param *param )
1097{
1098 const pjmedia_codec_info *info;
1099 pjmedia_codec_mgr *codec_mgr;
1100 unsigned count = 1;
1101 pj_status_t status;
1102
1103 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1104
1105 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
1106 &count, &info, NULL);
1107 if (status != PJ_SUCCESS)
1108 return status;
1109
1110 if (count != 1)
1111 return PJ_ENOTFOUND;
1112
1113 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
1114 return status;
1115}
1116
1117
1118/*
1119 * Set codec parameters.
1120 */
1121PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
1122 const pjmedia_codec_param *param)
1123{
Benny Prijono00cae612006-07-31 15:19:36 +00001124 PJ_UNUSED_ARG(id);
1125 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001126 PJ_TODO(set_codec_param);
1127 return PJ_SUCCESS;
1128}