blob: e37275a2c3787709d0f7dfc9bbc2667f8da65227 [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 {
Benny Prijono594e4c52006-09-14 18:51:01 +0000289 pj_in_addr addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000290
291 /* Get local IP address. */
Benny Prijono594e4c52006-09-14 18:51:01 +0000292 status = pj_gethostip(&addr);
293 if (status != PJ_SUCCESS)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000294 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000295
296 for (i=0; i<2; ++i)
Benny Prijono594e4c52006-09-14 18:51:01 +0000297 mapped_addr[i].sin_addr = addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000298
299 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)rtp_port);
300 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(rtp_port+1));
301 break;
302 }
303 }
304
305 if (sock[0] == PJ_INVALID_SOCKET) {
306 PJ_LOG(1,(THIS_FILE,
307 "Unable to find appropriate RTP/RTCP ports combination"));
308 goto on_error;
309 }
310
311
312 skinfo->rtp_sock = sock[0];
313 pj_memcpy(&skinfo->rtp_addr_name,
314 &mapped_addr[0], sizeof(pj_sockaddr_in));
315
316 skinfo->rtcp_sock = sock[1];
317 pj_memcpy(&skinfo->rtcp_addr_name,
318 &mapped_addr[1], sizeof(pj_sockaddr_in));
319
320 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d",
321 pj_inet_ntoa(skinfo->rtp_addr_name.sin_addr),
322 pj_ntohs(skinfo->rtp_addr_name.sin_port)));
323 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s:%d",
324 pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr),
325 pj_ntohs(skinfo->rtcp_addr_name.sin_port)));
326
327 rtp_port += 2;
328 return PJ_SUCCESS;
329
330on_error:
331 for (i=0; i<2; ++i) {
332 if (sock[i] != PJ_INVALID_SOCKET)
333 pj_sock_close(sock[i]);
334 }
335 return status;
336}
337
338
339/*
340 * Start pjsua media subsystem.
341 */
342pj_status_t pjsua_media_subsys_start(void)
343{
344 pj_status_t status;
345
346 /* Create media for calls, if none is specified */
347 if (pjsua_var.calls[0].med_tp == NULL) {
348 pjsua_transport_config transport_cfg;
349
350 /* Create default transport config */
351 pjsua_transport_config_default(&transport_cfg);
352 transport_cfg.port = DEFAULT_RTP_PORT;
353
354 status = pjsua_media_transports_create(&transport_cfg);
355 if (status != PJ_SUCCESS)
356 return status;
357 }
358
359 /* Create sound port if none is created yet */
Benny Prijonoe909eac2006-07-27 22:04:56 +0000360 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
361 !pjsua_var.no_snd)
362 {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000363 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
364 if (status != PJ_SUCCESS) {
365 /* Error opening sound device, use null device */
366 char errmsg[PJ_ERR_MSG_SIZE];
367
368 pj_strerror(status, errmsg, sizeof(errmsg));
369 PJ_LOG(4,(THIS_FILE,
370 "Error opening default sound device (%s (status=%d)). "
371 "Will use NULL device instead",
372 errmsg, status));
373
374 status = pjsua_set_null_snd_dev();
375 if (status != PJ_SUCCESS) {
376 pjsua_perror(THIS_FILE, "Error opening NULL sound device",
377 status);
378 return status;
379 }
380 }
381 }
382
383 return PJ_SUCCESS;
384}
385
386
387/*
388 * Destroy pjsua media subsystem.
389 */
390pj_status_t pjsua_media_subsys_destroy(void)
391{
392 unsigned i;
393
394 close_snd_dev();
395
396 if (pjsua_var.mconf) {
397 pjmedia_conf_destroy(pjsua_var.mconf);
398 pjsua_var.mconf = NULL;
399 }
400
401 if (pjsua_var.null_port) {
402 pjmedia_port_destroy(pjsua_var.null_port);
403 pjsua_var.null_port = NULL;
404 }
405
406 /* Destroy file players */
407 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
408 if (pjsua_var.player[i].port) {
409 pjmedia_port_destroy(pjsua_var.player[i].port);
410 pjsua_var.player[i].port = NULL;
411 }
412 }
413
414 /* Destroy file recorders */
415 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
416 if (pjsua_var.recorder[i].port) {
417 pjmedia_port_destroy(pjsua_var.recorder[i].port);
418 pjsua_var.recorder[i].port = NULL;
419 }
420 }
421
422 /* Close media transports */
423 for (i=0; i<(int)pjsua_var.ua_cfg.max_calls; ++i) {
424 if (pjsua_var.calls[i].med_tp) {
425 (*pjsua_var.calls[i].med_tp->op->destroy)(pjsua_var.calls[i].med_tp);
426 pjsua_var.calls[i].med_tp = NULL;
427 }
428 }
429
430 /* Destroy media endpoint. */
431 if (pjsua_var.med_endpt) {
432
433 /* Shutdown all codecs: */
434# if PJMEDIA_HAS_SPEEX_CODEC
435 pjmedia_codec_speex_deinit();
436# endif /* PJMEDIA_HAS_SPEEX_CODEC */
437
438# if PJMEDIA_HAS_GSM_CODEC
439 pjmedia_codec_gsm_deinit();
440# endif /* PJMEDIA_HAS_GSM_CODEC */
441
442# if PJMEDIA_HAS_G711_CODEC
443 pjmedia_codec_g711_deinit();
444# endif /* PJMEDIA_HAS_G711_CODEC */
445
446# if PJMEDIA_HAS_L16_CODEC
447 pjmedia_codec_l16_deinit();
448# endif /* PJMEDIA_HAS_L16_CODEC */
449
450
451 pjmedia_endpt_destroy(pjsua_var.med_endpt);
452 pjsua_var.med_endpt = NULL;
453 }
454
455 return PJ_SUCCESS;
456}
457
458
459/*
460 * Create UDP media transports for all the calls. This function creates
461 * one UDP media transport for each call.
462 */
463PJ_DEF(pj_status_t)
464pjsua_media_transports_create(const pjsua_transport_config *app_cfg)
465{
466 pjsua_transport_config cfg;
467 unsigned i;
468 pj_status_t status;
469
470
471 /* Make sure pjsua_init() has been called */
472 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
473
474 PJSUA_LOCK();
475
476 /* Delete existing media transports */
477 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
478 if (pjsua_var.calls[i].med_tp != NULL) {
479 pjsua_var.calls[i].med_tp->op->destroy(pjsua_var.calls[i].med_tp);
480 pjsua_var.calls[i].med_tp = NULL;
481 }
482 }
483
484 /* Copy config */
485 pj_memcpy(&cfg, app_cfg, sizeof(*app_cfg));
486 pjsua_normalize_stun_config(&cfg.stun_config);
487
488 /* Create each media transport */
489 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
490
491 status = create_rtp_rtcp_sock(&cfg, &pjsua_var.calls[i].skinfo);
492 if (status != PJ_SUCCESS) {
493 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
494 status);
495 goto on_error;
496 }
497 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
498 &pjsua_var.calls[i].skinfo, 0,
499 &pjsua_var.calls[i].med_tp);
500 if (status != PJ_SUCCESS) {
501 pjsua_perror(THIS_FILE, "Unable to create media transport",
502 status);
503 goto on_error;
504 }
Benny Prijono00cae612006-07-31 15:19:36 +0000505
506 pjmedia_transport_udp_simulate_lost(pjsua_var.calls[i].med_tp,
507 PJMEDIA_DIR_ENCODING,
508 pjsua_var.media_cfg.tx_drop_pct);
509
510 pjmedia_transport_udp_simulate_lost(pjsua_var.calls[i].med_tp,
511 PJMEDIA_DIR_DECODING,
512 pjsua_var.media_cfg.rx_drop_pct);
513
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000514 }
515
516 PJSUA_UNLOCK();
517
518 return PJ_SUCCESS;
519
520on_error:
521 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
522 if (pjsua_var.calls[i].med_tp != NULL) {
523 pjsua_var.calls[i].med_tp->op->destroy(pjsua_var.calls[i].med_tp);
524 pjsua_var.calls[i].med_tp = NULL;
525 }
526 }
527
528 PJSUA_UNLOCK();
529
530 return status;
531}
532
533
534/*
535 * Get maxinum number of conference ports.
536 */
537PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
538{
539 return pjsua_var.media_cfg.max_media_ports;
540}
541
542
543/*
544 * Get current number of active ports in the bridge.
545 */
546PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
547{
548 unsigned ports[256];
549 unsigned count = PJ_ARRAY_SIZE(ports);
550 pj_status_t status;
551
552 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
553 if (status != PJ_SUCCESS)
554 count = 0;
555
556 return count;
557}
558
559
560/*
561 * Enumerate all conference ports.
562 */
563PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
564 unsigned *count)
565{
566 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
567}
568
569
570/*
571 * Get information about the specified conference port
572 */
573PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
574 pjsua_conf_port_info *info)
575{
576 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +0000577 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000578 pj_status_t status;
579
580 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
581 if (status != PJ_SUCCESS)
582 return status;
583
Benny Prijonoac623b32006-07-03 15:19:31 +0000584 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000585 info->slot_id = id;
586 info->name = cinfo.name;
587 info->clock_rate = cinfo.clock_rate;
588 info->channel_count = cinfo.channel_count;
589 info->samples_per_frame = cinfo.samples_per_frame;
590 info->bits_per_sample = cinfo.bits_per_sample;
591
592 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +0000593 info->listener_cnt = cinfo.listener_cnt;
594 for (i=0; i<cinfo.listener_cnt; ++i) {
595 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000596 }
597
598 return PJ_SUCCESS;
599}
600
601
602/*
Benny Prijonoe909eac2006-07-27 22:04:56 +0000603 * Add arbitrary media port to PJSUA's conference bridge.
604 */
605PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
606 pjmedia_port *port,
607 pjsua_conf_port_id *p_id)
608{
609 pj_status_t status;
610
611 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
612 port, NULL, (unsigned*)p_id);
613 if (status != PJ_SUCCESS) {
614 if (p_id)
615 *p_id = PJSUA_INVALID_ID;
616 }
617
618 return status;
619}
620
621
622/*
623 * Remove arbitrary slot from the conference bridge.
624 */
625PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
626{
627 return pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
628}
629
630
631/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000632 * Establish unidirectional media flow from souce to sink.
633 */
634PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
635 pjsua_conf_port_id sink)
636{
637 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
638}
639
640
641/*
642 * Disconnect media flow from the source to destination port.
643 */
644PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
645 pjsua_conf_port_id sink)
646{
647 return pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
648}
649
650
651/*****************************************************************************
652 * File player.
653 */
654
655/*
656 * Create a file player, and automatically connect this player to
657 * the conference bridge.
658 */
659PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
660 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000661 pjsua_player_id *p_id)
662{
663 unsigned slot, file_id;
664 char path[128];
665 pjmedia_port *port;
666 pj_status_t status;
667
668 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
669 return PJ_ETOOMANY;
670
671 PJSUA_LOCK();
672
673 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
674 if (pjsua_var.player[file_id].port == NULL)
675 break;
676 }
677
678 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
679 /* This is unexpected */
680 PJSUA_UNLOCK();
681 pj_assert(0);
682 return PJ_EBUG;
683 }
684
685 pj_memcpy(path, filename->ptr, filename->slen);
686 path[filename->slen] = '\0';
687 status = pjmedia_wav_player_port_create(pjsua_var.pool, path,
688 pjsua_var.mconf_cfg.samples_per_frame *
689 1000 / pjsua_var.media_cfg.clock_rate,
Benny Prijono00cae612006-07-31 15:19:36 +0000690 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000691 if (status != PJ_SUCCESS) {
692 PJSUA_UNLOCK();
693 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
694 return status;
695 }
696
697 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
698 port, filename, &slot);
699 if (status != PJ_SUCCESS) {
700 pjmedia_port_destroy(port);
701 PJSUA_UNLOCK();
702 return status;
703 }
704
705 pjsua_var.player[file_id].port = port;
706 pjsua_var.player[file_id].slot = slot;
707
708 if (p_id) *p_id = file_id;
709
710 ++pjsua_var.player_cnt;
711
712 PJSUA_UNLOCK();
713 return PJ_SUCCESS;
714}
715
716
717/*
718 * Get conference port ID associated with player.
719 */
720PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
721{
722 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
723 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
724
725 return pjsua_var.player[id].slot;
726}
727
728
729/*
730 * Set playback position.
731 */
732PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
733 pj_uint32_t samples)
734{
735 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
736 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
737
738 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
739}
740
741
742/*
743 * Close the file, remove the player from the bridge, and free
744 * resources associated with the file player.
745 */
746PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
747{
748 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
749 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
750
751 PJSUA_LOCK();
752
753 if (pjsua_var.player[id].port) {
754 pjmedia_conf_remove_port(pjsua_var.mconf,
755 pjsua_var.player[id].slot);
756 pjmedia_port_destroy(pjsua_var.player[id].port);
757 pjsua_var.player[id].port = NULL;
758 pjsua_var.player[id].slot = 0xFFFF;
759 pjsua_var.player_cnt--;
760 }
761
762 PJSUA_UNLOCK();
763
764 return PJ_SUCCESS;
765}
766
767
768/*****************************************************************************
769 * File recorder.
770 */
771
772/*
773 * Create a file recorder, and automatically connect this recorder to
774 * the conference bridge.
775 */
776PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
777 unsigned file_format,
778 const pj_str_t *encoding,
779 pj_ssize_t max_size,
780 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000781 pjsua_recorder_id *p_id)
782{
783 unsigned slot, file_id;
784 char path[128];
785 pjmedia_port *port;
786 pj_status_t status;
787
Benny Prijono00cae612006-07-31 15:19:36 +0000788 /* Don't support max_size at present */
789 PJ_ASSERT_RETURN(max_size == 0, PJ_EINVAL);
790
791 /* Don't support file format at present */
792 PJ_ASSERT_RETURN(file_format == 0, PJ_EINVAL);
793
794 /* Don't support encoding at present */
795 PJ_ASSERT_RETURN(encoding == NULL, PJ_EINVAL);
796
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000797 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
798 return PJ_ETOOMANY;
799
800 PJSUA_LOCK();
801
802 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
803 if (pjsua_var.recorder[file_id].port == NULL)
804 break;
805 }
806
807 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
808 /* This is unexpected */
809 PJSUA_UNLOCK();
810 pj_assert(0);
811 return PJ_EBUG;
812 }
813
814 pj_memcpy(path, filename->ptr, filename->slen);
815 path[filename->slen] = '\0';
816 status = pjmedia_wav_writer_port_create(pjsua_var.pool, path,
817 pjsua_var.media_cfg.clock_rate,
818 pjsua_var.mconf_cfg.channel_count,
819 pjsua_var.mconf_cfg.samples_per_frame,
820 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono00cae612006-07-31 15:19:36 +0000821 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000822 if (status != PJ_SUCCESS) {
823 PJSUA_UNLOCK();
824 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
825 return status;
826 }
827
828 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
829 port, filename, &slot);
830 if (status != PJ_SUCCESS) {
831 pjmedia_port_destroy(port);
832 PJSUA_UNLOCK();
833 return status;
834 }
835
836 pjsua_var.recorder[file_id].port = port;
837 pjsua_var.recorder[file_id].slot = slot;
838
839 if (p_id) *p_id = file_id;
840
841 ++pjsua_var.rec_cnt;
842
843 PJSUA_UNLOCK();
844 return PJ_SUCCESS;
845}
846
847
848/*
849 * Get conference port associated with recorder.
850 */
851PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
852{
853 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL);
854 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
855
856 return pjsua_var.recorder[id].slot;
857}
858
859
860/*
861 * Destroy recorder (this will complete recording).
862 */
863PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
864{
865 PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL);
866 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
867
868 PJSUA_LOCK();
869
870 if (pjsua_var.recorder[id].port) {
871 pjmedia_conf_remove_port(pjsua_var.mconf,
872 pjsua_var.recorder[id].slot);
873 pjmedia_port_destroy(pjsua_var.recorder[id].port);
874 pjsua_var.recorder[id].port = NULL;
875 pjsua_var.recorder[id].slot = 0xFFFF;
876 pjsua_var.rec_cnt--;
877 }
878
879 PJSUA_UNLOCK();
880
881 return PJ_SUCCESS;
882}
883
884
885/*****************************************************************************
886 * Sound devices.
887 */
888
889/*
890 * Enum sound devices.
891 */
892PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
893 unsigned *count)
894{
895 unsigned i, dev_count;
896
897 dev_count = pjmedia_snd_get_dev_count();
898
899 if (dev_count > *count) dev_count = *count;
900
901 for (i=0; i<dev_count; ++i) {
902 const pjmedia_snd_dev_info *ci;
903
904 ci = pjmedia_snd_get_dev_info(i);
905 pj_memcpy(&info[i], ci, sizeof(*ci));
906 }
907
908 *count = dev_count;
909
910 return PJ_SUCCESS;
911}
912
913
914/* Close existing sound device */
915static void close_snd_dev(void)
916{
917 /* Close sound device */
918 if (pjsua_var.snd_port) {
919 const pjmedia_snd_dev_info *cap_info, *play_info;
920
921 cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
922 play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
923
924 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
925 "%s sound capture device",
926 play_info->name, cap_info->name));
927
928 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
929 pjmedia_snd_port_destroy(pjsua_var.snd_port);
930 pjsua_var.snd_port = NULL;
931 }
932
933 /* Close null sound device */
934 if (pjsua_var.null_snd) {
935 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
936 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
937 pjsua_var.null_snd = NULL;
938 }
939}
940
941/*
942 * Select or change sound device. Application may call this function at
943 * any time to replace current sound device.
944 */
945PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
946 int playback_dev)
947{
948 pjmedia_port *conf_port;
949 const pjmedia_snd_dev_info *cap_info, *play_info;
950 pj_status_t status;
951
952 /* Close existing sound port */
953 close_snd_dev();
954
955
956 cap_info = pjmedia_snd_get_dev_info(capture_dev);
957 play_info = pjmedia_snd_get_dev_info(playback_dev);
958
959 PJ_LOG(4,(THIS_FILE, "Opening %s sound playback device and "
960 "%s sound capture device..",
961 play_info->name, cap_info->name));
962
963 /* Create the sound device. Sound port will start immediately. */
964 status = pjmedia_snd_port_create(pjsua_var.pool, capture_dev,
965 playback_dev,
966 pjsua_var.media_cfg.clock_rate, 1,
967 pjsua_var.media_cfg.clock_rate/FPS,
968 16, 0, &pjsua_var.snd_port);
969 if (status != PJ_SUCCESS) {
970 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
971 return status;
972 }
973
974 /* Get the port0 of the conference bridge. */
975 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
976 pj_assert(conf_port != NULL);
977
Benny Prijonof20687a2006-08-04 18:27:19 +0000978 /* Set AEC */
Benny Prijono5da50432006-08-07 10:24:52 +0000979 pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
980 pjsua_var.media_cfg.ec_tail_len,
981 pjsua_var.media_cfg.ec_options);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000982
Benny Prijono52a93912006-08-04 20:54:37 +0000983 /* Connect sound port to the bridge */
984 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
985 conf_port );
986 if (status != PJ_SUCCESS) {
987 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
988 "sound device", status);
989 pjmedia_snd_port_destroy(pjsua_var.snd_port);
990 pjsua_var.snd_port = NULL;
991 return status;
992 }
993
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000994 /* Save the device IDs */
995 pjsua_var.cap_dev = capture_dev;
996 pjsua_var.play_dev = playback_dev;
997
998 return PJ_SUCCESS;
999}
1000
1001
1002/*
1003 * Use null sound device.
1004 */
1005PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
1006{
1007 pjmedia_port *conf_port;
1008 pj_status_t status;
1009
1010 /* Close existing sound device */
1011 close_snd_dev();
1012
1013 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
1014
1015 /* Get the port0 of the conference bridge. */
1016 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1017 pj_assert(conf_port != NULL);
1018
1019 /* Create master port, connecting port0 of the conference bridge to
1020 * a null port.
1021 */
1022 status = pjmedia_master_port_create(pjsua_var.pool, pjsua_var.null_port,
1023 conf_port, 0, &pjsua_var.null_snd);
1024 if (status != PJ_SUCCESS) {
1025 pjsua_perror(THIS_FILE, "Unable to create null sound device",
1026 status);
1027 return status;
1028 }
1029
1030 /* Start the master port */
1031 status = pjmedia_master_port_start(pjsua_var.null_snd);
1032 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
1033
1034 return PJ_SUCCESS;
1035}
1036
1037
Benny Prijonoe909eac2006-07-27 22:04:56 +00001038
1039/*
1040 * Use no device!
1041 */
1042PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
1043{
1044 /* Close existing sound device */
1045 close_snd_dev();
1046
1047 pjsua_var.no_snd = PJ_TRUE;
1048 return pjmedia_conf_get_master_port(pjsua_var.mconf);
1049}
1050
1051
Benny Prijonof20687a2006-08-04 18:27:19 +00001052/*
1053 * Configure the AEC settings of the sound port.
1054 */
Benny Prijono5da50432006-08-07 10:24:52 +00001055PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00001056{
1057 pjsua_var.media_cfg.ec_tail_len = tail_ms;
1058
1059 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00001060 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
1061 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00001062
1063 return PJ_SUCCESS;
1064}
1065
1066
1067/*
1068 * Get current AEC tail length.
1069 */
Benny Prijono22dfe592006-08-06 12:07:13 +00001070PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00001071{
1072 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
1073 return PJ_SUCCESS;
1074}
1075
Benny Prijonoe909eac2006-07-27 22:04:56 +00001076
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001077/*****************************************************************************
1078 * Codecs.
1079 */
1080
1081/*
1082 * Enum all supported codecs in the system.
1083 */
1084PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
1085 unsigned *p_count )
1086{
1087 pjmedia_codec_mgr *codec_mgr;
1088 pjmedia_codec_info info[32];
1089 unsigned i, count, prio[32];
1090 pj_status_t status;
1091
1092 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1093 count = PJ_ARRAY_SIZE(info);
1094 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
1095 if (status != PJ_SUCCESS) {
1096 *p_count = 0;
1097 return status;
1098 }
1099
1100 if (count > *p_count) count = *p_count;
1101
1102 for (i=0; i<count; ++i) {
1103 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
1104 id[i].codec_id = pj_str(id[i].buf_);
1105 id[i].priority = (pj_uint8_t) prio[i];
1106 }
1107
1108 *p_count = count;
1109
1110 return PJ_SUCCESS;
1111}
1112
1113
1114/*
1115 * Change codec priority.
1116 */
1117PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
1118 pj_uint8_t priority )
1119{
1120 pjmedia_codec_mgr *codec_mgr;
1121
1122 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1123
1124 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
1125 priority);
1126}
1127
1128
1129/*
1130 * Get codec parameters.
1131 */
1132PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
1133 pjmedia_codec_param *param )
1134{
1135 const pjmedia_codec_info *info;
1136 pjmedia_codec_mgr *codec_mgr;
1137 unsigned count = 1;
1138 pj_status_t status;
1139
1140 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1141
1142 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
1143 &count, &info, NULL);
1144 if (status != PJ_SUCCESS)
1145 return status;
1146
1147 if (count != 1)
1148 return PJ_ENOTFOUND;
1149
1150 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
1151 return status;
1152}
1153
1154
1155/*
1156 * Set codec parameters.
1157 */
1158PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
1159 const pjmedia_codec_param *param)
1160{
Benny Prijono00cae612006-07-31 15:19:36 +00001161 PJ_UNUSED_ARG(id);
1162 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001163 PJ_TODO(set_codec_param);
1164 return PJ_SUCCESS;
1165}