blob: 0cf1d1def20dfd8282e3b0da009d7f9cf1db412e [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 */
Benny Prijono0875ae82006-12-26 00:11:48 +0000445 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000446 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
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000477 /* Deinitialize sound subsystem */
478 pjmedia_snd_deinit();
479
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000480 return PJ_SUCCESS;
481}
482
483
484/*
485 * Create UDP media transports for all the calls. This function creates
486 * one UDP media transport for each call.
487 */
488PJ_DEF(pj_status_t)
489pjsua_media_transports_create(const pjsua_transport_config *app_cfg)
490{
491 pjsua_transport_config cfg;
492 unsigned i;
493 pj_status_t status;
494
495
496 /* Make sure pjsua_init() has been called */
497 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
498
499 PJSUA_LOCK();
500
501 /* Delete existing media transports */
502 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
503 if (pjsua_var.calls[i].med_tp != NULL) {
504 pjsua_var.calls[i].med_tp->op->destroy(pjsua_var.calls[i].med_tp);
505 pjsua_var.calls[i].med_tp = NULL;
506 }
507 }
508
509 /* Copy config */
510 pj_memcpy(&cfg, app_cfg, sizeof(*app_cfg));
511 pjsua_normalize_stun_config(&cfg.stun_config);
512
513 /* Create each media transport */
514 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
515
516 status = create_rtp_rtcp_sock(&cfg, &pjsua_var.calls[i].skinfo);
517 if (status != PJ_SUCCESS) {
518 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
519 status);
520 goto on_error;
521 }
522 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
523 &pjsua_var.calls[i].skinfo, 0,
524 &pjsua_var.calls[i].med_tp);
525 if (status != PJ_SUCCESS) {
526 pjsua_perror(THIS_FILE, "Unable to create media transport",
527 status);
528 goto on_error;
529 }
Benny Prijono00cae612006-07-31 15:19:36 +0000530
531 pjmedia_transport_udp_simulate_lost(pjsua_var.calls[i].med_tp,
532 PJMEDIA_DIR_ENCODING,
533 pjsua_var.media_cfg.tx_drop_pct);
534
535 pjmedia_transport_udp_simulate_lost(pjsua_var.calls[i].med_tp,
536 PJMEDIA_DIR_DECODING,
537 pjsua_var.media_cfg.rx_drop_pct);
538
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000539 }
540
541 PJSUA_UNLOCK();
542
543 return PJ_SUCCESS;
544
545on_error:
546 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
547 if (pjsua_var.calls[i].med_tp != NULL) {
548 pjsua_var.calls[i].med_tp->op->destroy(pjsua_var.calls[i].med_tp);
549 pjsua_var.calls[i].med_tp = NULL;
550 }
551 }
552
553 PJSUA_UNLOCK();
554
555 return status;
556}
557
558
559/*
560 * Get maxinum number of conference ports.
561 */
562PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
563{
564 return pjsua_var.media_cfg.max_media_ports;
565}
566
567
568/*
569 * Get current number of active ports in the bridge.
570 */
571PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
572{
573 unsigned ports[256];
574 unsigned count = PJ_ARRAY_SIZE(ports);
575 pj_status_t status;
576
577 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
578 if (status != PJ_SUCCESS)
579 count = 0;
580
581 return count;
582}
583
584
585/*
586 * Enumerate all conference ports.
587 */
588PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
589 unsigned *count)
590{
591 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
592}
593
594
595/*
596 * Get information about the specified conference port
597 */
598PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
599 pjsua_conf_port_info *info)
600{
601 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +0000602 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000603 pj_status_t status;
604
605 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
606 if (status != PJ_SUCCESS)
607 return status;
608
Benny Prijonoac623b32006-07-03 15:19:31 +0000609 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000610 info->slot_id = id;
611 info->name = cinfo.name;
612 info->clock_rate = cinfo.clock_rate;
613 info->channel_count = cinfo.channel_count;
614 info->samples_per_frame = cinfo.samples_per_frame;
615 info->bits_per_sample = cinfo.bits_per_sample;
616
617 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +0000618 info->listener_cnt = cinfo.listener_cnt;
619 for (i=0; i<cinfo.listener_cnt; ++i) {
620 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000621 }
622
623 return PJ_SUCCESS;
624}
625
626
627/*
Benny Prijonoe909eac2006-07-27 22:04:56 +0000628 * Add arbitrary media port to PJSUA's conference bridge.
629 */
630PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
631 pjmedia_port *port,
632 pjsua_conf_port_id *p_id)
633{
634 pj_status_t status;
635
636 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
637 port, NULL, (unsigned*)p_id);
638 if (status != PJ_SUCCESS) {
639 if (p_id)
640 *p_id = PJSUA_INVALID_ID;
641 }
642
643 return status;
644}
645
646
647/*
648 * Remove arbitrary slot from the conference bridge.
649 */
650PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
651{
652 return pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
653}
654
655
656/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000657 * Establish unidirectional media flow from souce to sink.
658 */
659PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
660 pjsua_conf_port_id sink)
661{
662 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
663}
664
665
666/*
667 * Disconnect media flow from the source to destination port.
668 */
669PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
670 pjsua_conf_port_id sink)
671{
672 return pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
673}
674
675
Benny Prijono6dd967c2006-12-26 02:27:14 +0000676/*
677 * Adjust the signal level to be transmitted from the bridge to the
678 * specified port by making it louder or quieter.
679 */
680PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
681 float level)
682{
683 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
684 (int)((level-1) * 128));
685}
686
687/*
688 * Adjust the signal level to be received from the specified port (to
689 * the bridge) by making it louder or quieter.
690 */
691PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
692 float level)
693{
694 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
695 (int)((level-1) * 128));
696}
697
698
699/*
700 * Get last signal level transmitted to or received from the specified port.
701 */
702PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
703 unsigned *tx_level,
704 unsigned *rx_level)
705{
706 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
707 tx_level, rx_level);
708}
709
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000710/*****************************************************************************
711 * File player.
712 */
713
714/*
715 * Create a file player, and automatically connect this player to
716 * the conference bridge.
717 */
718PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
719 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000720 pjsua_player_id *p_id)
721{
722 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +0000723 char path[PJ_MAXPATH];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000724 pjmedia_port *port;
725 pj_status_t status;
726
727 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
728 return PJ_ETOOMANY;
729
730 PJSUA_LOCK();
731
732 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
733 if (pjsua_var.player[file_id].port == NULL)
734 break;
735 }
736
737 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
738 /* This is unexpected */
739 PJSUA_UNLOCK();
740 pj_assert(0);
741 return PJ_EBUG;
742 }
743
744 pj_memcpy(path, filename->ptr, filename->slen);
745 path[filename->slen] = '\0';
746 status = pjmedia_wav_player_port_create(pjsua_var.pool, path,
747 pjsua_var.mconf_cfg.samples_per_frame *
748 1000 / pjsua_var.media_cfg.clock_rate,
Benny Prijono00cae612006-07-31 15:19:36 +0000749 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000750 if (status != PJ_SUCCESS) {
751 PJSUA_UNLOCK();
752 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
753 return status;
754 }
755
756 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
757 port, filename, &slot);
758 if (status != PJ_SUCCESS) {
759 pjmedia_port_destroy(port);
760 PJSUA_UNLOCK();
761 return status;
762 }
763
Benny Prijonoa66c3312007-01-21 23:12:40 +0000764 pjsua_var.player[file_id].type = 0;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000765 pjsua_var.player[file_id].port = port;
766 pjsua_var.player[file_id].slot = slot;
767
768 if (p_id) *p_id = file_id;
769
770 ++pjsua_var.player_cnt;
771
772 PJSUA_UNLOCK();
773 return PJ_SUCCESS;
774}
775
776
777/*
Benny Prijonoa66c3312007-01-21 23:12:40 +0000778 * Create a file playlist media port, and automatically add the port
779 * to the conference bridge.
780 */
781PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
782 unsigned file_count,
783 const pj_str_t *label,
784 unsigned options,
785 pjsua_player_id *p_id)
786{
787 unsigned slot, file_id, ptime;
788 pjmedia_port *port;
789 pj_status_t status;
790
791 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
792 return PJ_ETOOMANY;
793
794 PJSUA_LOCK();
795
796 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
797 if (pjsua_var.player[file_id].port == NULL)
798 break;
799 }
800
801 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
802 /* This is unexpected */
803 PJSUA_UNLOCK();
804 pj_assert(0);
805 return PJ_EBUG;
806 }
807
808
809 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
810 pjsua_var.media_cfg.clock_rate;
811
812 status = pjmedia_wav_playlist_create(pjsua_var.pool, label,
813 file_names, file_count,
814 ptime, options, 0, &port);
815 if (status != PJ_SUCCESS) {
816 PJSUA_UNLOCK();
817 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
818 return status;
819 }
820
821 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
822 port, &port->info.name, &slot);
823 if (status != PJ_SUCCESS) {
824 pjmedia_port_destroy(port);
825 PJSUA_UNLOCK();
826 pjsua_perror(THIS_FILE, "Unable to add port", status);
827 return status;
828 }
829
830 pjsua_var.player[file_id].type = 1;
831 pjsua_var.player[file_id].port = port;
832 pjsua_var.player[file_id].slot = slot;
833
834 if (p_id) *p_id = file_id;
835
836 ++pjsua_var.player_cnt;
837
838 PJSUA_UNLOCK();
839 return PJ_SUCCESS;
840
841}
842
843
844/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000845 * Get conference port ID associated with player.
846 */
847PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
848{
849 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
850 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
851
852 return pjsua_var.player[id].slot;
853}
854
Benny Prijono469b1522006-12-26 03:05:17 +0000855/*
856 * Get the media port for the player.
857 */
858PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_recorder_id id,
859 pjmedia_port **p_port)
860{
861 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
862 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
863 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
864
865 *p_port = pjsua_var.player[id].port;
866
867 return PJ_SUCCESS;
868}
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000869
870/*
871 * Set playback position.
872 */
873PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
874 pj_uint32_t samples)
875{
876 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
877 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +0000878 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000879
880 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
881}
882
883
884/*
885 * Close the file, remove the player from the bridge, and free
886 * resources associated with the file player.
887 */
888PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
889{
890 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
891 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
892
893 PJSUA_LOCK();
894
895 if (pjsua_var.player[id].port) {
896 pjmedia_conf_remove_port(pjsua_var.mconf,
897 pjsua_var.player[id].slot);
898 pjmedia_port_destroy(pjsua_var.player[id].port);
899 pjsua_var.player[id].port = NULL;
900 pjsua_var.player[id].slot = 0xFFFF;
901 pjsua_var.player_cnt--;
902 }
903
904 PJSUA_UNLOCK();
905
906 return PJ_SUCCESS;
907}
908
909
910/*****************************************************************************
911 * File recorder.
912 */
913
914/*
915 * Create a file recorder, and automatically connect this recorder to
916 * the conference bridge.
917 */
918PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +0000919 unsigned enc_type,
920 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000921 pj_ssize_t max_size,
922 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000923 pjsua_recorder_id *p_id)
924{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +0000925 enum Format
926 {
927 FMT_UNKNOWN,
928 FMT_WAV,
929 FMT_MP3,
930 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000931 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +0000932 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +0000933 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +0000934 int file_format;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000935 pjmedia_port *port;
936 pj_status_t status;
937
Benny Prijonob3cdb2b2006-10-19 15:49:47 +0000938 /* Filename must present */
939 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
940
Benny Prijono00cae612006-07-31 15:19:36 +0000941 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +0000942 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +0000943
Benny Prijono8f310522006-10-20 11:08:49 +0000944 /* Don't support encoding type at present */
945 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +0000946
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000947 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
948 return PJ_ETOOMANY;
949
Benny Prijonob3cdb2b2006-10-19 15:49:47 +0000950 /* Determine the file format */
951 ext.ptr = filename->ptr + filename->slen - 4;
952 ext.slen = 4;
953
954 if (pj_stricmp2(&ext, ".wav") == 0)
955 file_format = FMT_WAV;
956 else if (pj_stricmp2(&ext, ".mp3") == 0)
957 file_format = FMT_MP3;
958 else {
959 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
960 "determine file format for %.*s",
961 (int)filename->slen, filename->ptr));
962 return PJ_ENOTSUP;
963 }
964
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000965 PJSUA_LOCK();
966
967 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
968 if (pjsua_var.recorder[file_id].port == NULL)
969 break;
970 }
971
972 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
973 /* This is unexpected */
974 PJSUA_UNLOCK();
975 pj_assert(0);
976 return PJ_EBUG;
977 }
978
979 pj_memcpy(path, filename->ptr, filename->slen);
980 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +0000981
982 if (file_format == FMT_WAV) {
983 status = pjmedia_wav_writer_port_create(pjsua_var.pool, path,
984 pjsua_var.media_cfg.clock_rate,
985 pjsua_var.mconf_cfg.channel_count,
986 pjsua_var.mconf_cfg.samples_per_frame,
987 pjsua_var.mconf_cfg.bits_per_sample,
988 options, 0, &port);
989 } else if (file_format == FMT_MP3) {
990 status = pjmedia_mp3_writer_port_create(pjsua_var.pool, path,
991 pjsua_var.media_cfg.clock_rate,
992 pjsua_var.mconf_cfg.channel_count,
993 pjsua_var.mconf_cfg.samples_per_frame,
994 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono8f310522006-10-20 11:08:49 +0000995 enc_param, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +0000996 } else {
997 port = NULL;
998 status = PJ_ENOTSUP;
999 }
1000
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001001 if (status != PJ_SUCCESS) {
1002 PJSUA_UNLOCK();
1003 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
1004 return status;
1005 }
1006
1007 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
1008 port, filename, &slot);
1009 if (status != PJ_SUCCESS) {
1010 pjmedia_port_destroy(port);
1011 PJSUA_UNLOCK();
1012 return status;
1013 }
1014
1015 pjsua_var.recorder[file_id].port = port;
1016 pjsua_var.recorder[file_id].slot = slot;
1017
1018 if (p_id) *p_id = file_id;
1019
1020 ++pjsua_var.rec_cnt;
1021
1022 PJSUA_UNLOCK();
1023 return PJ_SUCCESS;
1024}
1025
1026
1027/*
1028 * Get conference port associated with recorder.
1029 */
1030PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
1031{
1032 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL);
1033 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1034
1035 return pjsua_var.recorder[id].slot;
1036}
1037
Benny Prijono469b1522006-12-26 03:05:17 +00001038/*
1039 * Get the media port for the recorder.
1040 */
1041PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
1042 pjmedia_port **p_port)
1043{
1044 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL);
1045 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1046 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1047
1048 *p_port = pjsua_var.recorder[id].port;
1049 return PJ_SUCCESS;
1050}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001051
1052/*
1053 * Destroy recorder (this will complete recording).
1054 */
1055PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
1056{
1057 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL);
1058 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1059
1060 PJSUA_LOCK();
1061
1062 if (pjsua_var.recorder[id].port) {
1063 pjmedia_conf_remove_port(pjsua_var.mconf,
1064 pjsua_var.recorder[id].slot);
1065 pjmedia_port_destroy(pjsua_var.recorder[id].port);
1066 pjsua_var.recorder[id].port = NULL;
1067 pjsua_var.recorder[id].slot = 0xFFFF;
1068 pjsua_var.rec_cnt--;
1069 }
1070
1071 PJSUA_UNLOCK();
1072
1073 return PJ_SUCCESS;
1074}
1075
1076
1077/*****************************************************************************
1078 * Sound devices.
1079 */
1080
1081/*
1082 * Enum sound devices.
1083 */
1084PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
1085 unsigned *count)
1086{
1087 unsigned i, dev_count;
1088
1089 dev_count = pjmedia_snd_get_dev_count();
1090
1091 if (dev_count > *count) dev_count = *count;
1092
1093 for (i=0; i<dev_count; ++i) {
1094 const pjmedia_snd_dev_info *ci;
1095
1096 ci = pjmedia_snd_get_dev_info(i);
1097 pj_memcpy(&info[i], ci, sizeof(*ci));
1098 }
1099
1100 *count = dev_count;
1101
1102 return PJ_SUCCESS;
1103}
1104
1105
1106/* Close existing sound device */
1107static void close_snd_dev(void)
1108{
1109 /* Close sound device */
1110 if (pjsua_var.snd_port) {
1111 const pjmedia_snd_dev_info *cap_info, *play_info;
1112
1113 cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
1114 play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
1115
1116 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
1117 "%s sound capture device",
1118 play_info->name, cap_info->name));
1119
1120 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
1121 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1122 pjsua_var.snd_port = NULL;
1123 }
1124
1125 /* Close null sound device */
1126 if (pjsua_var.null_snd) {
1127 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
1128 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
1129 pjsua_var.null_snd = NULL;
1130 }
1131}
1132
1133/*
1134 * Select or change sound device. Application may call this function at
1135 * any time to replace current sound device.
1136 */
1137PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
1138 int playback_dev)
1139{
1140 pjmedia_port *conf_port;
Benny Prijono6dd967c2006-12-26 02:27:14 +00001141 const pjmedia_snd_dev_info *play_info;
Benny Prijono26056d82006-10-11 16:03:41 +00001142 unsigned clock_rates[] = { 0, 22050, 44100, 48000, 11025, 32000, 8000};
Benny Prijono658a1c52006-10-11 21:56:16 +00001143 unsigned selected_clock_rate = 0;
Benny Prijono26056d82006-10-11 16:03:41 +00001144 unsigned i;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00001145 pjmedia_snd_stream *strm;
1146 pjmedia_snd_stream_info si;
1147 pj_str_t tmp;
Benny Prijono26056d82006-10-11 16:03:41 +00001148 pj_status_t status = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001149
1150 /* Close existing sound port */
1151 close_snd_dev();
1152
1153
Benny Prijono26056d82006-10-11 16:03:41 +00001154 /* Set default clock rate */
1155 clock_rates[0] = pjsua_var.media_cfg.clock_rate;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001156
Benny Prijono26056d82006-10-11 16:03:41 +00001157 /* Attempts to open the sound device with different clock rates */
1158 for (i=0; i<PJ_ARRAY_SIZE(clock_rates); ++i) {
1159 char errmsg[PJ_ERR_MSG_SIZE];
1160
1161 PJ_LOG(4,(THIS_FILE,
1162 "pjsua_set_snd_dev(): attempting to open devices "
1163 "@%d Hz", clock_rates[i]));
1164
1165 /* Create the sound device. Sound port will start immediately. */
1166 status = pjmedia_snd_port_create(pjsua_var.pool, capture_dev,
1167 playback_dev,
1168 clock_rates[i], 1,
1169 clock_rates[i]/FPS,
1170 16, 0, &pjsua_var.snd_port);
1171
Benny Prijono658a1c52006-10-11 21:56:16 +00001172 if (status == PJ_SUCCESS) {
1173 selected_clock_rate = clock_rates[i];
Benny Prijono26056d82006-10-11 16:03:41 +00001174 break;
Benny Prijono658a1c52006-10-11 21:56:16 +00001175 }
Benny Prijono26056d82006-10-11 16:03:41 +00001176
1177 pj_strerror(status, errmsg, sizeof(errmsg));
Benny Prijono658a1c52006-10-11 21:56:16 +00001178 PJ_LOG(4, (THIS_FILE, "..failed: %s", errmsg));
Benny Prijono26056d82006-10-11 16:03:41 +00001179 }
1180
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001181 if (status != PJ_SUCCESS) {
1182 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
1183 return status;
1184 }
1185
1186 /* Get the port0 of the conference bridge. */
1187 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1188 pj_assert(conf_port != NULL);
1189
Benny Prijonof20687a2006-08-04 18:27:19 +00001190 /* Set AEC */
Benny Prijono5da50432006-08-07 10:24:52 +00001191 pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
1192 pjsua_var.media_cfg.ec_tail_len,
1193 pjsua_var.media_cfg.ec_options);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001194
Benny Prijono658a1c52006-10-11 21:56:16 +00001195 /* If there's mismatch between sound port and conference's port,
1196 * create a resample port to bridge them.
1197 */
1198 if (selected_clock_rate != pjsua_var.media_cfg.clock_rate) {
1199 pjmedia_port *resample_port;
1200
1201 status = pjmedia_resample_port_create(pjsua_var.pool, conf_port,
1202 selected_clock_rate, 0,
1203 &resample_port);
1204 if (status != PJ_SUCCESS) {
1205 pjsua_perror("Error creating resample port", THIS_FILE, status);
1206 return status;
1207 }
1208
1209 conf_port = resample_port;
1210 }
1211
Benny Prijono52a93912006-08-04 20:54:37 +00001212 /* Connect sound port to the bridge */
1213 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
1214 conf_port );
1215 if (status != PJ_SUCCESS) {
1216 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
1217 "sound device", status);
1218 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1219 pjsua_var.snd_port = NULL;
1220 return status;
1221 }
1222
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001223 /* Save the device IDs */
1224 pjsua_var.cap_dev = capture_dev;
1225 pjsua_var.play_dev = playback_dev;
1226
Benny Prijonoc53c6d72006-11-27 09:54:03 +00001227 /* Update sound device name. */
1228 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
1229 pjmedia_snd_stream_get_info(strm, &si);
1230 play_info = pjmedia_snd_get_dev_info(si.rec_id);
1231
1232 pjmedia_conf_set_port0_name(pjsua_var.mconf,
1233 pj_cstr(&tmp, play_info->name));
1234
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001235 return PJ_SUCCESS;
1236}
1237
1238
1239/*
1240 * Use null sound device.
1241 */
1242PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
1243{
1244 pjmedia_port *conf_port;
1245 pj_status_t status;
1246
1247 /* Close existing sound device */
1248 close_snd_dev();
1249
1250 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
1251
1252 /* Get the port0 of the conference bridge. */
1253 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1254 pj_assert(conf_port != NULL);
1255
1256 /* Create master port, connecting port0 of the conference bridge to
1257 * a null port.
1258 */
1259 status = pjmedia_master_port_create(pjsua_var.pool, pjsua_var.null_port,
1260 conf_port, 0, &pjsua_var.null_snd);
1261 if (status != PJ_SUCCESS) {
1262 pjsua_perror(THIS_FILE, "Unable to create null sound device",
1263 status);
1264 return status;
1265 }
1266
1267 /* Start the master port */
1268 status = pjmedia_master_port_start(pjsua_var.null_snd);
1269 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
1270
1271 return PJ_SUCCESS;
1272}
1273
1274
Benny Prijonoe909eac2006-07-27 22:04:56 +00001275
1276/*
1277 * Use no device!
1278 */
1279PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
1280{
1281 /* Close existing sound device */
1282 close_snd_dev();
1283
1284 pjsua_var.no_snd = PJ_TRUE;
1285 return pjmedia_conf_get_master_port(pjsua_var.mconf);
1286}
1287
1288
Benny Prijonof20687a2006-08-04 18:27:19 +00001289/*
1290 * Configure the AEC settings of the sound port.
1291 */
Benny Prijono5da50432006-08-07 10:24:52 +00001292PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00001293{
1294 pjsua_var.media_cfg.ec_tail_len = tail_ms;
1295
1296 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00001297 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
1298 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00001299
1300 return PJ_SUCCESS;
1301}
1302
1303
1304/*
1305 * Get current AEC tail length.
1306 */
Benny Prijono22dfe592006-08-06 12:07:13 +00001307PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00001308{
1309 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
1310 return PJ_SUCCESS;
1311}
1312
Benny Prijonoe909eac2006-07-27 22:04:56 +00001313
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001314/*****************************************************************************
1315 * Codecs.
1316 */
1317
1318/*
1319 * Enum all supported codecs in the system.
1320 */
1321PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
1322 unsigned *p_count )
1323{
1324 pjmedia_codec_mgr *codec_mgr;
1325 pjmedia_codec_info info[32];
1326 unsigned i, count, prio[32];
1327 pj_status_t status;
1328
1329 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1330 count = PJ_ARRAY_SIZE(info);
1331 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
1332 if (status != PJ_SUCCESS) {
1333 *p_count = 0;
1334 return status;
1335 }
1336
1337 if (count > *p_count) count = *p_count;
1338
1339 for (i=0; i<count; ++i) {
1340 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
1341 id[i].codec_id = pj_str(id[i].buf_);
1342 id[i].priority = (pj_uint8_t) prio[i];
1343 }
1344
1345 *p_count = count;
1346
1347 return PJ_SUCCESS;
1348}
1349
1350
1351/*
1352 * Change codec priority.
1353 */
1354PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
1355 pj_uint8_t priority )
1356{
1357 pjmedia_codec_mgr *codec_mgr;
1358
1359 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1360
1361 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
1362 priority);
1363}
1364
1365
1366/*
1367 * Get codec parameters.
1368 */
1369PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
1370 pjmedia_codec_param *param )
1371{
1372 const pjmedia_codec_info *info;
1373 pjmedia_codec_mgr *codec_mgr;
1374 unsigned count = 1;
1375 pj_status_t status;
1376
1377 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1378
1379 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
1380 &count, &info, NULL);
1381 if (status != PJ_SUCCESS)
1382 return status;
1383
1384 if (count != 1)
1385 return PJ_ENOTFOUND;
1386
1387 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
1388 return status;
1389}
1390
1391
1392/*
1393 * Set codec parameters.
1394 */
1395PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
1396 const pjmedia_codec_param *param)
1397{
Benny Prijono00cae612006-07-31 15:19:36 +00001398 PJ_UNUSED_ARG(id);
1399 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001400 PJ_TODO(set_codec_param);
1401 return PJ_SUCCESS;
1402}