blob: 13ff542cbde2e0e8249d25f3adbe5d3751e9f994 [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;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000212 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000213 pj_sockaddr_in mapped_addr[2];
214 pj_status_t status = PJ_SUCCESS;
215 pj_sock_t sock[2];
216
217 if (rtp_port == 0)
218 rtp_port = (pj_uint16_t)cfg->port;
219
220 for (i=0; i<2; ++i)
221 sock[i] = PJ_INVALID_SOCKET;
222
Benny Prijono0a5cad82006-09-26 13:21:02 +0000223 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
224 if (cfg->bound_addr.slen) {
225 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
226 if (status != PJ_SUCCESS) {
227 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
228 status);
229 return status;
230 }
231 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000232
233 /* Loop retry to bind RTP and RTCP sockets. */
234 for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) {
235
236 /* Create and bind RTP socket. */
237 status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[0]);
238 if (status != PJ_SUCCESS) {
239 pjsua_perror(THIS_FILE, "socket() error", status);
240 return status;
241 }
242
Benny Prijono0a5cad82006-09-26 13:21:02 +0000243 status = pj_sock_bind_in(sock[0], bound_addr.sin_addr.s_addr,
244 rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000245 if (status != PJ_SUCCESS) {
246 pj_sock_close(sock[0]);
247 sock[0] = PJ_INVALID_SOCKET;
248 continue;
249 }
250
251 /* Create and bind RTCP socket. */
252 status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[1]);
253 if (status != PJ_SUCCESS) {
254 pjsua_perror(THIS_FILE, "socket() error", status);
255 pj_sock_close(sock[0]);
256 return status;
257 }
258
Benny Prijono0a5cad82006-09-26 13:21:02 +0000259 status = pj_sock_bind_in(sock[1], bound_addr.sin_addr.s_addr,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000260 (pj_uint16_t)(rtp_port+1));
261 if (status != PJ_SUCCESS) {
262 pj_sock_close(sock[0]);
263 sock[0] = PJ_INVALID_SOCKET;
264
265 pj_sock_close(sock[1]);
266 sock[1] = PJ_INVALID_SOCKET;
267 continue;
268 }
269
270 /*
271 * If we're configured to use STUN, then find out the mapped address,
272 * and make sure that the mapped RTCP port is adjacent with the RTP.
273 */
274 if (cfg->stun_config.stun_srv1.slen) {
275 status=pj_stun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
276 &cfg->stun_config.stun_srv1,
277 cfg->stun_config.stun_port1,
278 &cfg->stun_config.stun_srv2,
279 cfg->stun_config.stun_port2,
280 mapped_addr);
281 if (status != PJ_SUCCESS) {
282 pjsua_perror(THIS_FILE, "STUN resolve error", status);
283 goto on_error;
284 }
285
286 if (pj_ntohs(mapped_addr[1].sin_port) ==
287 pj_ntohs(mapped_addr[0].sin_port)+1)
288 {
289 /* Success! */
290 break;
291 }
292
293 pj_sock_close(sock[0]);
294 sock[0] = PJ_INVALID_SOCKET;
295
296 pj_sock_close(sock[1]);
297 sock[1] = PJ_INVALID_SOCKET;
298
Benny Prijono0a5cad82006-09-26 13:21:02 +0000299 } else if (cfg->public_addr.slen) {
300
301 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
302 (pj_uint16_t)rtp_port);
303 if (status != PJ_SUCCESS)
304 goto on_error;
305
306 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
307 (pj_uint16_t)(rtp_port+1));
308 if (status != PJ_SUCCESS)
309 goto on_error;
310
311 break;
312
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000313 } else {
Benny Prijono594e4c52006-09-14 18:51:01 +0000314 pj_in_addr addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000315
316 /* Get local IP address. */
Benny Prijono594e4c52006-09-14 18:51:01 +0000317 status = pj_gethostip(&addr);
318 if (status != PJ_SUCCESS)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000319 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000320
321 for (i=0; i<2; ++i)
Benny Prijono594e4c52006-09-14 18:51:01 +0000322 mapped_addr[i].sin_addr = addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000323
324 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)rtp_port);
325 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(rtp_port+1));
326 break;
327 }
328 }
329
330 if (sock[0] == PJ_INVALID_SOCKET) {
331 PJ_LOG(1,(THIS_FILE,
332 "Unable to find appropriate RTP/RTCP ports combination"));
333 goto on_error;
334 }
335
336
337 skinfo->rtp_sock = sock[0];
338 pj_memcpy(&skinfo->rtp_addr_name,
339 &mapped_addr[0], sizeof(pj_sockaddr_in));
340
341 skinfo->rtcp_sock = sock[1];
342 pj_memcpy(&skinfo->rtcp_addr_name,
343 &mapped_addr[1], sizeof(pj_sockaddr_in));
344
345 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d",
346 pj_inet_ntoa(skinfo->rtp_addr_name.sin_addr),
347 pj_ntohs(skinfo->rtp_addr_name.sin_port)));
348 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s:%d",
349 pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr),
350 pj_ntohs(skinfo->rtcp_addr_name.sin_port)));
351
352 rtp_port += 2;
353 return PJ_SUCCESS;
354
355on_error:
356 for (i=0; i<2; ++i) {
357 if (sock[i] != PJ_INVALID_SOCKET)
358 pj_sock_close(sock[i]);
359 }
360 return status;
361}
362
363
364/*
365 * Start pjsua media subsystem.
366 */
367pj_status_t pjsua_media_subsys_start(void)
368{
369 pj_status_t status;
370
371 /* Create media for calls, if none is specified */
372 if (pjsua_var.calls[0].med_tp == NULL) {
373 pjsua_transport_config transport_cfg;
374
375 /* Create default transport config */
376 pjsua_transport_config_default(&transport_cfg);
377 transport_cfg.port = DEFAULT_RTP_PORT;
378
379 status = pjsua_media_transports_create(&transport_cfg);
380 if (status != PJ_SUCCESS)
381 return status;
382 }
383
384 /* Create sound port if none is created yet */
Benny Prijonoe909eac2006-07-27 22:04:56 +0000385 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
386 !pjsua_var.no_snd)
387 {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000388 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
389 if (status != PJ_SUCCESS) {
390 /* Error opening sound device, use null device */
391 char errmsg[PJ_ERR_MSG_SIZE];
392
393 pj_strerror(status, errmsg, sizeof(errmsg));
394 PJ_LOG(4,(THIS_FILE,
395 "Error opening default sound device (%s (status=%d)). "
396 "Will use NULL device instead",
397 errmsg, status));
398
399 status = pjsua_set_null_snd_dev();
400 if (status != PJ_SUCCESS) {
401 pjsua_perror(THIS_FILE, "Error opening NULL sound device",
402 status);
403 return status;
404 }
405 }
406 }
407
408 return PJ_SUCCESS;
409}
410
411
412/*
413 * Destroy pjsua media subsystem.
414 */
415pj_status_t pjsua_media_subsys_destroy(void)
416{
417 unsigned i;
418
419 close_snd_dev();
420
421 if (pjsua_var.mconf) {
422 pjmedia_conf_destroy(pjsua_var.mconf);
423 pjsua_var.mconf = NULL;
424 }
425
426 if (pjsua_var.null_port) {
427 pjmedia_port_destroy(pjsua_var.null_port);
428 pjsua_var.null_port = NULL;
429 }
430
431 /* Destroy file players */
432 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
433 if (pjsua_var.player[i].port) {
434 pjmedia_port_destroy(pjsua_var.player[i].port);
435 pjsua_var.player[i].port = NULL;
436 }
437 }
438
439 /* Destroy file recorders */
440 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
441 if (pjsua_var.recorder[i].port) {
442 pjmedia_port_destroy(pjsua_var.recorder[i].port);
443 pjsua_var.recorder[i].port = NULL;
444 }
445 }
446
447 /* Close media transports */
448 for (i=0; i<(int)pjsua_var.ua_cfg.max_calls; ++i) {
449 if (pjsua_var.calls[i].med_tp) {
450 (*pjsua_var.calls[i].med_tp->op->destroy)(pjsua_var.calls[i].med_tp);
451 pjsua_var.calls[i].med_tp = NULL;
452 }
453 }
454
455 /* Destroy media endpoint. */
456 if (pjsua_var.med_endpt) {
457
458 /* Shutdown all codecs: */
459# if PJMEDIA_HAS_SPEEX_CODEC
460 pjmedia_codec_speex_deinit();
461# endif /* PJMEDIA_HAS_SPEEX_CODEC */
462
463# if PJMEDIA_HAS_GSM_CODEC
464 pjmedia_codec_gsm_deinit();
465# endif /* PJMEDIA_HAS_GSM_CODEC */
466
467# if PJMEDIA_HAS_G711_CODEC
468 pjmedia_codec_g711_deinit();
469# endif /* PJMEDIA_HAS_G711_CODEC */
470
471# if PJMEDIA_HAS_L16_CODEC
472 pjmedia_codec_l16_deinit();
473# endif /* PJMEDIA_HAS_L16_CODEC */
474
475
476 pjmedia_endpt_destroy(pjsua_var.med_endpt);
477 pjsua_var.med_endpt = NULL;
478 }
479
480 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
676/*****************************************************************************
677 * File player.
678 */
679
680/*
681 * Create a file player, and automatically connect this player to
682 * the conference bridge.
683 */
684PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
685 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000686 pjsua_player_id *p_id)
687{
688 unsigned slot, file_id;
689 char path[128];
690 pjmedia_port *port;
691 pj_status_t status;
692
693 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
694 return PJ_ETOOMANY;
695
696 PJSUA_LOCK();
697
698 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
699 if (pjsua_var.player[file_id].port == NULL)
700 break;
701 }
702
703 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
704 /* This is unexpected */
705 PJSUA_UNLOCK();
706 pj_assert(0);
707 return PJ_EBUG;
708 }
709
710 pj_memcpy(path, filename->ptr, filename->slen);
711 path[filename->slen] = '\0';
712 status = pjmedia_wav_player_port_create(pjsua_var.pool, path,
713 pjsua_var.mconf_cfg.samples_per_frame *
714 1000 / pjsua_var.media_cfg.clock_rate,
Benny Prijono00cae612006-07-31 15:19:36 +0000715 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000716 if (status != PJ_SUCCESS) {
717 PJSUA_UNLOCK();
718 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
719 return status;
720 }
721
722 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
723 port, filename, &slot);
724 if (status != PJ_SUCCESS) {
725 pjmedia_port_destroy(port);
726 PJSUA_UNLOCK();
727 return status;
728 }
729
730 pjsua_var.player[file_id].port = port;
731 pjsua_var.player[file_id].slot = slot;
732
733 if (p_id) *p_id = file_id;
734
735 ++pjsua_var.player_cnt;
736
737 PJSUA_UNLOCK();
738 return PJ_SUCCESS;
739}
740
741
742/*
743 * Get conference port ID associated with player.
744 */
745PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
746{
747 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
748 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
749
750 return pjsua_var.player[id].slot;
751}
752
753
754/*
755 * Set playback position.
756 */
757PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
758 pj_uint32_t samples)
759{
760 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
761 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
762
763 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
764}
765
766
767/*
768 * Close the file, remove the player from the bridge, and free
769 * resources associated with the file player.
770 */
771PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
772{
773 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
774 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
775
776 PJSUA_LOCK();
777
778 if (pjsua_var.player[id].port) {
779 pjmedia_conf_remove_port(pjsua_var.mconf,
780 pjsua_var.player[id].slot);
781 pjmedia_port_destroy(pjsua_var.player[id].port);
782 pjsua_var.player[id].port = NULL;
783 pjsua_var.player[id].slot = 0xFFFF;
784 pjsua_var.player_cnt--;
785 }
786
787 PJSUA_UNLOCK();
788
789 return PJ_SUCCESS;
790}
791
792
793/*****************************************************************************
794 * File recorder.
795 */
796
797/*
798 * Create a file recorder, and automatically connect this recorder to
799 * the conference bridge.
800 */
801PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
802 unsigned file_format,
803 const pj_str_t *encoding,
804 pj_ssize_t max_size,
805 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000806 pjsua_recorder_id *p_id)
807{
808 unsigned slot, file_id;
809 char path[128];
810 pjmedia_port *port;
811 pj_status_t status;
812
Benny Prijono00cae612006-07-31 15:19:36 +0000813 /* Don't support max_size at present */
814 PJ_ASSERT_RETURN(max_size == 0, PJ_EINVAL);
815
816 /* Don't support file format at present */
817 PJ_ASSERT_RETURN(file_format == 0, PJ_EINVAL);
818
819 /* Don't support encoding at present */
820 PJ_ASSERT_RETURN(encoding == NULL, PJ_EINVAL);
821
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000822 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
823 return PJ_ETOOMANY;
824
825 PJSUA_LOCK();
826
827 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
828 if (pjsua_var.recorder[file_id].port == NULL)
829 break;
830 }
831
832 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
833 /* This is unexpected */
834 PJSUA_UNLOCK();
835 pj_assert(0);
836 return PJ_EBUG;
837 }
838
839 pj_memcpy(path, filename->ptr, filename->slen);
840 path[filename->slen] = '\0';
841 status = pjmedia_wav_writer_port_create(pjsua_var.pool, path,
842 pjsua_var.media_cfg.clock_rate,
843 pjsua_var.mconf_cfg.channel_count,
844 pjsua_var.mconf_cfg.samples_per_frame,
845 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono00cae612006-07-31 15:19:36 +0000846 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000847 if (status != PJ_SUCCESS) {
848 PJSUA_UNLOCK();
849 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
850 return status;
851 }
852
853 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
854 port, filename, &slot);
855 if (status != PJ_SUCCESS) {
856 pjmedia_port_destroy(port);
857 PJSUA_UNLOCK();
858 return status;
859 }
860
861 pjsua_var.recorder[file_id].port = port;
862 pjsua_var.recorder[file_id].slot = slot;
863
864 if (p_id) *p_id = file_id;
865
866 ++pjsua_var.rec_cnt;
867
868 PJSUA_UNLOCK();
869 return PJ_SUCCESS;
870}
871
872
873/*
874 * Get conference port associated with recorder.
875 */
876PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
877{
878 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL);
879 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
880
881 return pjsua_var.recorder[id].slot;
882}
883
884
885/*
886 * Destroy recorder (this will complete recording).
887 */
888PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
889{
890 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL);
891 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
892
893 PJSUA_LOCK();
894
895 if (pjsua_var.recorder[id].port) {
896 pjmedia_conf_remove_port(pjsua_var.mconf,
897 pjsua_var.recorder[id].slot);
898 pjmedia_port_destroy(pjsua_var.recorder[id].port);
899 pjsua_var.recorder[id].port = NULL;
900 pjsua_var.recorder[id].slot = 0xFFFF;
901 pjsua_var.rec_cnt--;
902 }
903
904 PJSUA_UNLOCK();
905
906 return PJ_SUCCESS;
907}
908
909
910/*****************************************************************************
911 * Sound devices.
912 */
913
914/*
915 * Enum sound devices.
916 */
917PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
918 unsigned *count)
919{
920 unsigned i, dev_count;
921
922 dev_count = pjmedia_snd_get_dev_count();
923
924 if (dev_count > *count) dev_count = *count;
925
926 for (i=0; i<dev_count; ++i) {
927 const pjmedia_snd_dev_info *ci;
928
929 ci = pjmedia_snd_get_dev_info(i);
930 pj_memcpy(&info[i], ci, sizeof(*ci));
931 }
932
933 *count = dev_count;
934
935 return PJ_SUCCESS;
936}
937
938
939/* Close existing sound device */
940static void close_snd_dev(void)
941{
942 /* Close sound device */
943 if (pjsua_var.snd_port) {
944 const pjmedia_snd_dev_info *cap_info, *play_info;
945
946 cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
947 play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
948
949 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
950 "%s sound capture device",
951 play_info->name, cap_info->name));
952
953 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
954 pjmedia_snd_port_destroy(pjsua_var.snd_port);
955 pjsua_var.snd_port = NULL;
956 }
957
958 /* Close null sound device */
959 if (pjsua_var.null_snd) {
960 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
961 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
962 pjsua_var.null_snd = NULL;
963 }
964}
965
966/*
967 * Select or change sound device. Application may call this function at
968 * any time to replace current sound device.
969 */
970PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
971 int playback_dev)
972{
973 pjmedia_port *conf_port;
974 const pjmedia_snd_dev_info *cap_info, *play_info;
975 pj_status_t status;
976
977 /* Close existing sound port */
978 close_snd_dev();
979
980
981 cap_info = pjmedia_snd_get_dev_info(capture_dev);
982 play_info = pjmedia_snd_get_dev_info(playback_dev);
983
984 PJ_LOG(4,(THIS_FILE, "Opening %s sound playback device and "
985 "%s sound capture device..",
986 play_info->name, cap_info->name));
987
988 /* Create the sound device. Sound port will start immediately. */
989 status = pjmedia_snd_port_create(pjsua_var.pool, capture_dev,
990 playback_dev,
991 pjsua_var.media_cfg.clock_rate, 1,
992 pjsua_var.media_cfg.clock_rate/FPS,
993 16, 0, &pjsua_var.snd_port);
994 if (status != PJ_SUCCESS) {
995 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
996 return status;
997 }
998
999 /* Get the port0 of the conference bridge. */
1000 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1001 pj_assert(conf_port != NULL);
1002
Benny Prijonof20687a2006-08-04 18:27:19 +00001003 /* Set AEC */
Benny Prijono5da50432006-08-07 10:24:52 +00001004 pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
1005 pjsua_var.media_cfg.ec_tail_len,
1006 pjsua_var.media_cfg.ec_options);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001007
Benny Prijono52a93912006-08-04 20:54:37 +00001008 /* Connect sound port to the bridge */
1009 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
1010 conf_port );
1011 if (status != PJ_SUCCESS) {
1012 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
1013 "sound device", status);
1014 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1015 pjsua_var.snd_port = NULL;
1016 return status;
1017 }
1018
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001019 /* Save the device IDs */
1020 pjsua_var.cap_dev = capture_dev;
1021 pjsua_var.play_dev = playback_dev;
1022
1023 return PJ_SUCCESS;
1024}
1025
1026
1027/*
1028 * Use null sound device.
1029 */
1030PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
1031{
1032 pjmedia_port *conf_port;
1033 pj_status_t status;
1034
1035 /* Close existing sound device */
1036 close_snd_dev();
1037
1038 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
1039
1040 /* Get the port0 of the conference bridge. */
1041 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1042 pj_assert(conf_port != NULL);
1043
1044 /* Create master port, connecting port0 of the conference bridge to
1045 * a null port.
1046 */
1047 status = pjmedia_master_port_create(pjsua_var.pool, pjsua_var.null_port,
1048 conf_port, 0, &pjsua_var.null_snd);
1049 if (status != PJ_SUCCESS) {
1050 pjsua_perror(THIS_FILE, "Unable to create null sound device",
1051 status);
1052 return status;
1053 }
1054
1055 /* Start the master port */
1056 status = pjmedia_master_port_start(pjsua_var.null_snd);
1057 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
1058
1059 return PJ_SUCCESS;
1060}
1061
1062
Benny Prijonoe909eac2006-07-27 22:04:56 +00001063
1064/*
1065 * Use no device!
1066 */
1067PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
1068{
1069 /* Close existing sound device */
1070 close_snd_dev();
1071
1072 pjsua_var.no_snd = PJ_TRUE;
1073 return pjmedia_conf_get_master_port(pjsua_var.mconf);
1074}
1075
1076
Benny Prijonof20687a2006-08-04 18:27:19 +00001077/*
1078 * Configure the AEC settings of the sound port.
1079 */
Benny Prijono5da50432006-08-07 10:24:52 +00001080PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00001081{
1082 pjsua_var.media_cfg.ec_tail_len = tail_ms;
1083
1084 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00001085 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
1086 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00001087
1088 return PJ_SUCCESS;
1089}
1090
1091
1092/*
1093 * Get current AEC tail length.
1094 */
Benny Prijono22dfe592006-08-06 12:07:13 +00001095PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00001096{
1097 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
1098 return PJ_SUCCESS;
1099}
1100
Benny Prijonoe909eac2006-07-27 22:04:56 +00001101
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001102/*****************************************************************************
1103 * Codecs.
1104 */
1105
1106/*
1107 * Enum all supported codecs in the system.
1108 */
1109PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
1110 unsigned *p_count )
1111{
1112 pjmedia_codec_mgr *codec_mgr;
1113 pjmedia_codec_info info[32];
1114 unsigned i, count, prio[32];
1115 pj_status_t status;
1116
1117 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1118 count = PJ_ARRAY_SIZE(info);
1119 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
1120 if (status != PJ_SUCCESS) {
1121 *p_count = 0;
1122 return status;
1123 }
1124
1125 if (count > *p_count) count = *p_count;
1126
1127 for (i=0; i<count; ++i) {
1128 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
1129 id[i].codec_id = pj_str(id[i].buf_);
1130 id[i].priority = (pj_uint8_t) prio[i];
1131 }
1132
1133 *p_count = count;
1134
1135 return PJ_SUCCESS;
1136}
1137
1138
1139/*
1140 * Change codec priority.
1141 */
1142PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
1143 pj_uint8_t priority )
1144{
1145 pjmedia_codec_mgr *codec_mgr;
1146
1147 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1148
1149 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
1150 priority);
1151}
1152
1153
1154/*
1155 * Get codec parameters.
1156 */
1157PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
1158 pjmedia_codec_param *param )
1159{
1160 const pjmedia_codec_info *info;
1161 pjmedia_codec_mgr *codec_mgr;
1162 unsigned count = 1;
1163 pj_status_t status;
1164
1165 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1166
1167 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
1168 &count, &info, NULL);
1169 if (status != PJ_SUCCESS)
1170 return status;
1171
1172 if (count != 1)
1173 return PJ_ENOTFOUND;
1174
1175 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
1176 return status;
1177}
1178
1179
1180/*
1181 * Set codec parameters.
1182 */
1183PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
1184 const pjmedia_codec_param *param)
1185{
Benny Prijono00cae612006-07-31 15:19:36 +00001186 PJ_UNUSED_ARG(id);
1187 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001188 PJ_TODO(set_codec_param);
1189 return PJ_SUCCESS;
1190}