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