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