blob: 16dcbce80fc3b9c740edc42a02f0d800e04b41cc [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
Benny Prijonoa771a512007-02-19 01:13:53 +00003 * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004 *
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
Benny Prijono80eee892007-11-03 22:43:23 +000025#define DEFAULT_RTP_PORT 4000
26
27#ifndef PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
28# define PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT 0
29#endif
30
Benny Prijonoeebe9af2006-06-13 22:57:13 +000031
Benny Prijonode479562007-03-15 10:23:55 +000032/* Next RTP port to be used */
33static pj_uint16_t next_rtp_port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000034
35/* Close existing sound device */
36static void close_snd_dev(void);
37
38
39/**
40 * Init media subsystems.
41 */
42pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
43{
Benny Prijonoba5926a2007-05-02 11:29:37 +000044 pj_str_t codec_id = {NULL, 0};
Benny Prijono0498d902006-06-19 14:49:14 +000045 unsigned opt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000046 pj_status_t status;
47
Benny Prijonofc24e692007-01-27 18:31:51 +000048 /* To suppress warning about unused var when all codecs are disabled */
49 PJ_UNUSED_ARG(codec_id);
50
Benny Prijonoeebe9af2006-06-13 22:57:13 +000051 /* Copy configuration */
52 pj_memcpy(&pjsua_var.media_cfg, cfg, sizeof(*cfg));
53
54 /* Normalize configuration */
Benny Prijonoeebe9af2006-06-13 22:57:13 +000055
56 if (pjsua_var.media_cfg.has_ioqueue &&
57 pjsua_var.media_cfg.thread_cnt == 0)
58 {
59 pjsua_var.media_cfg.thread_cnt = 1;
60 }
61
62 if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
63 pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
64 }
65
66 /* Create media endpoint. */
67 status = pjmedia_endpt_create(&pjsua_var.cp.factory,
68 pjsua_var.media_cfg.has_ioqueue? NULL :
69 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
70 pjsua_var.media_cfg.thread_cnt,
71 &pjsua_var.med_endpt);
72 if (status != PJ_SUCCESS) {
73 pjsua_perror(THIS_FILE,
74 "Media stack initialization has returned error",
75 status);
76 return status;
77 }
78
79 /* Register all codecs */
80#if PJMEDIA_HAS_SPEEX_CODEC
81 /* Register speex. */
82 status = pjmedia_codec_speex_init(pjsua_var.med_endpt,
Benny Prijono7ca96da2006-08-07 12:11:40 +000083 0,
Benny Prijono0498d902006-06-19 14:49:14 +000084 pjsua_var.media_cfg.quality,
85 pjsua_var.media_cfg.quality);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000086 if (status != PJ_SUCCESS) {
87 pjsua_perror(THIS_FILE, "Error initializing Speex codec",
88 status);
89 return status;
90 }
Benny Prijono7ca96da2006-08-07 12:11:40 +000091
92 /* Set speex/16000 to higher priority*/
93 codec_id = pj_str("speex/16000");
94 pjmedia_codec_mgr_set_codec_priority(
95 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
96 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
97
98 /* Set speex/8000 to next higher priority*/
99 codec_id = pj_str("speex/8000");
100 pjmedia_codec_mgr_set_codec_priority(
101 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
102 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
103
104
105
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000106#endif /* PJMEDIA_HAS_SPEEX_CODEC */
107
Benny Prijono00cae612006-07-31 15:19:36 +0000108#if PJMEDIA_HAS_ILBC_CODEC
109 /* Register iLBC. */
110 status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt,
111 pjsua_var.media_cfg.ilbc_mode);
112 if (status != PJ_SUCCESS) {
113 pjsua_perror(THIS_FILE, "Error initializing iLBC codec",
114 status);
115 return status;
116 }
117#endif /* PJMEDIA_HAS_ILBC_CODEC */
118
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000119#if PJMEDIA_HAS_GSM_CODEC
120 /* Register GSM */
121 status = pjmedia_codec_gsm_init(pjsua_var.med_endpt);
122 if (status != PJ_SUCCESS) {
123 pjsua_perror(THIS_FILE, "Error initializing GSM codec",
124 status);
125 return status;
126 }
127#endif /* PJMEDIA_HAS_GSM_CODEC */
128
129#if PJMEDIA_HAS_G711_CODEC
130 /* Register PCMA and PCMU */
131 status = pjmedia_codec_g711_init(pjsua_var.med_endpt);
132 if (status != PJ_SUCCESS) {
133 pjsua_perror(THIS_FILE, "Error initializing G711 codec",
134 status);
135 return status;
136 }
137#endif /* PJMEDIA_HAS_G711_CODEC */
138
139#if PJMEDIA_HAS_L16_CODEC
140 /* Register L16 family codecs, but disable all */
141 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
142 if (status != PJ_SUCCESS) {
143 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
144 status);
145 return status;
146 }
147
148 /* Disable ALL L16 codecs */
149 codec_id = pj_str("L16");
150 pjmedia_codec_mgr_set_codec_priority(
151 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
152 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
153
154#endif /* PJMEDIA_HAS_L16_CODEC */
155
156
157 /* Save additional conference bridge parameters for future
158 * reference.
159 */
160 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
Benny Prijonocf0b4b22007-10-06 17:31:09 +0000161 pjsua_var.media_cfg.audio_frame_ptime /
162 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000163 pjsua_var.mconf_cfg.channel_count = 1;
164 pjsua_var.mconf_cfg.bits_per_sample = 16;
165
Benny Prijono0498d902006-06-19 14:49:14 +0000166 /* Init options for conference bridge. */
167 opt = PJMEDIA_CONF_NO_DEVICE;
168 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000169 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000170 {
171 opt |= PJMEDIA_CONF_SMALL_FILTER;
172 }
173 else if (pjsua_var.media_cfg.quality < 3) {
174 opt |= PJMEDIA_CONF_USE_LINEAR;
175 }
176
177
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000178 /* Init conference bridge. */
179 status = pjmedia_conf_create(pjsua_var.pool,
180 pjsua_var.media_cfg.max_media_ports,
181 pjsua_var.media_cfg.clock_rate,
182 pjsua_var.mconf_cfg.channel_count,
183 pjsua_var.mconf_cfg.samples_per_frame,
184 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000185 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000186 if (status != PJ_SUCCESS) {
187 pjsua_perror(THIS_FILE,
188 "Media stack initialization has returned error",
189 status);
190 return status;
191 }
192
193 /* Create null port just in case user wants to use null sound. */
194 status = pjmedia_null_port_create(pjsua_var.pool,
195 pjsua_var.media_cfg.clock_rate,
196 pjsua_var.mconf_cfg.channel_count,
197 pjsua_var.mconf_cfg.samples_per_frame,
198 pjsua_var.mconf_cfg.bits_per_sample,
199 &pjsua_var.null_port);
200 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
201
Benny Prijono6ba8c542007-10-16 01:34:14 +0000202 /* Perform NAT detection */
203 pjsua_detect_nat_type();
204
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000205 return PJ_SUCCESS;
206}
207
208
209/*
210 * Create RTP and RTCP socket pair, and possibly resolve their public
211 * address via STUN.
212 */
213static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
214 pjmedia_sock_info *skinfo)
215{
216 enum {
217 RTP_RETRY = 100
218 };
219 int i;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000220 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000221 pj_sockaddr_in mapped_addr[2];
222 pj_status_t status = PJ_SUCCESS;
223 pj_sock_t sock[2];
224
Benny Prijonoc97608e2007-03-23 16:34:20 +0000225 /* Make sure STUN server resolution has completed */
226 status = pjsua_resolve_stun_server(PJ_TRUE);
227 if (status != PJ_SUCCESS) {
228 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
229 return status;
230 }
231
232
Benny Prijonode479562007-03-15 10:23:55 +0000233 if (next_rtp_port == 0)
234 next_rtp_port = (pj_uint16_t)cfg->port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000235
236 for (i=0; i<2; ++i)
237 sock[i] = PJ_INVALID_SOCKET;
238
Benny Prijono0a5cad82006-09-26 13:21:02 +0000239 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
240 if (cfg->bound_addr.slen) {
241 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
242 if (status != PJ_SUCCESS) {
243 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
244 status);
245 return status;
246 }
247 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000248
249 /* Loop retry to bind RTP and RTCP sockets. */
Benny Prijonode479562007-03-15 10:23:55 +0000250 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000251
252 /* Create and bind RTP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000253 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000254 if (status != PJ_SUCCESS) {
255 pjsua_perror(THIS_FILE, "socket() error", status);
256 return status;
257 }
258
Benny Prijono0a5cad82006-09-26 13:21:02 +0000259 status = pj_sock_bind_in(sock[0], bound_addr.sin_addr.s_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000260 next_rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000261 if (status != PJ_SUCCESS) {
262 pj_sock_close(sock[0]);
263 sock[0] = PJ_INVALID_SOCKET;
264 continue;
265 }
266
267 /* Create and bind RTCP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000268 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000269 if (status != PJ_SUCCESS) {
270 pjsua_perror(THIS_FILE, "socket() error", status);
271 pj_sock_close(sock[0]);
272 return status;
273 }
274
Benny Prijono0a5cad82006-09-26 13:21:02 +0000275 status = pj_sock_bind_in(sock[1], bound_addr.sin_addr.s_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000276 (pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000277 if (status != PJ_SUCCESS) {
278 pj_sock_close(sock[0]);
279 sock[0] = PJ_INVALID_SOCKET;
280
281 pj_sock_close(sock[1]);
282 sock[1] = PJ_INVALID_SOCKET;
283 continue;
284 }
285
286 /*
287 * If we're configured to use STUN, then find out the mapped address,
288 * and make sure that the mapped RTCP port is adjacent with the RTP.
289 */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000290 if (pjsua_var.stun_srv.addr.sa_family != 0) {
291 char ip_addr[32];
292 pj_str_t stun_srv;
293
294 pj_ansi_strcpy(ip_addr,
295 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
296 stun_srv = pj_str(ip_addr);
297
Benny Prijono14c2b862007-02-21 00:40:05 +0000298 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000299 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
300 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000301 mapped_addr);
302 if (status != PJ_SUCCESS) {
303 pjsua_perror(THIS_FILE, "STUN resolve error", status);
304 goto on_error;
305 }
306
Benny Prijono80eee892007-11-03 22:43:23 +0000307#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000308 if (pj_ntohs(mapped_addr[1].sin_port) ==
309 pj_ntohs(mapped_addr[0].sin_port)+1)
310 {
311 /* Success! */
312 break;
313 }
314
315 pj_sock_close(sock[0]);
316 sock[0] = PJ_INVALID_SOCKET;
317
318 pj_sock_close(sock[1]);
319 sock[1] = PJ_INVALID_SOCKET;
Benny Prijono80eee892007-11-03 22:43:23 +0000320#else
321 if (pj_ntohs(mapped_addr[1].sin_port) !=
322 pj_ntohs(mapped_addr[0].sin_port)+1)
323 {
324 PJ_LOG(4,(THIS_FILE,
325 "Note: STUN mapped RTCP port %d is not adjacent"
326 " to RTP port %d",
327 pj_ntohs(mapped_addr[1].sin_port),
328 pj_ntohs(mapped_addr[0].sin_port)));
329 }
330 /* Success! */
331 break;
332#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000333
Benny Prijono0a5cad82006-09-26 13:21:02 +0000334 } else if (cfg->public_addr.slen) {
335
336 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000337 (pj_uint16_t)next_rtp_port);
Benny Prijono0a5cad82006-09-26 13:21:02 +0000338 if (status != PJ_SUCCESS)
339 goto on_error;
340
341 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000342 (pj_uint16_t)(next_rtp_port+1));
Benny Prijono0a5cad82006-09-26 13:21:02 +0000343 if (status != PJ_SUCCESS)
344 goto on_error;
345
346 break;
347
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000348 } else {
Benny Prijono594e4c52006-09-14 18:51:01 +0000349 pj_in_addr addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000350
351 /* Get local IP address. */
Benny Prijono594e4c52006-09-14 18:51:01 +0000352 status = pj_gethostip(&addr);
353 if (status != PJ_SUCCESS)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000354 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000355
356 for (i=0; i<2; ++i)
Benny Prijono594e4c52006-09-14 18:51:01 +0000357 mapped_addr[i].sin_addr = addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000358
Benny Prijonode479562007-03-15 10:23:55 +0000359 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
360 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000361 break;
362 }
363 }
364
365 if (sock[0] == PJ_INVALID_SOCKET) {
366 PJ_LOG(1,(THIS_FILE,
367 "Unable to find appropriate RTP/RTCP ports combination"));
368 goto on_error;
369 }
370
371
372 skinfo->rtp_sock = sock[0];
373 pj_memcpy(&skinfo->rtp_addr_name,
374 &mapped_addr[0], sizeof(pj_sockaddr_in));
375
376 skinfo->rtcp_sock = sock[1];
377 pj_memcpy(&skinfo->rtcp_addr_name,
378 &mapped_addr[1], sizeof(pj_sockaddr_in));
379
380 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d",
381 pj_inet_ntoa(skinfo->rtp_addr_name.sin_addr),
382 pj_ntohs(skinfo->rtp_addr_name.sin_port)));
383 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s:%d",
384 pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr),
385 pj_ntohs(skinfo->rtcp_addr_name.sin_port)));
386
Benny Prijonode479562007-03-15 10:23:55 +0000387 next_rtp_port += 2;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000388 return PJ_SUCCESS;
389
390on_error:
391 for (i=0; i<2; ++i) {
392 if (sock[i] != PJ_INVALID_SOCKET)
393 pj_sock_close(sock[i]);
394 }
395 return status;
396}
397
398
399/*
400 * Start pjsua media subsystem.
401 */
402pj_status_t pjsua_media_subsys_start(void)
403{
404 pj_status_t status;
405
406 /* Create media for calls, if none is specified */
407 if (pjsua_var.calls[0].med_tp == NULL) {
408 pjsua_transport_config transport_cfg;
409
410 /* Create default transport config */
411 pjsua_transport_config_default(&transport_cfg);
412 transport_cfg.port = DEFAULT_RTP_PORT;
413
414 status = pjsua_media_transports_create(&transport_cfg);
415 if (status != PJ_SUCCESS)
416 return status;
417 }
418
419 /* Create sound port if none is created yet */
Benny Prijonoe909eac2006-07-27 22:04:56 +0000420 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
421 !pjsua_var.no_snd)
422 {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000423 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
424 if (status != PJ_SUCCESS) {
Benny Prijonod639efa2007-04-05 22:45:06 +0000425 pjsua_perror(THIS_FILE, "Error opening sound device", status);
426 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000427 }
428 }
429
430 return PJ_SUCCESS;
431}
432
433
434/*
435 * Destroy pjsua media subsystem.
436 */
437pj_status_t pjsua_media_subsys_destroy(void)
438{
439 unsigned i;
440
441 close_snd_dev();
442
443 if (pjsua_var.mconf) {
444 pjmedia_conf_destroy(pjsua_var.mconf);
445 pjsua_var.mconf = NULL;
446 }
447
448 if (pjsua_var.null_port) {
449 pjmedia_port_destroy(pjsua_var.null_port);
450 pjsua_var.null_port = NULL;
451 }
452
453 /* Destroy file players */
454 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
455 if (pjsua_var.player[i].port) {
456 pjmedia_port_destroy(pjsua_var.player[i].port);
457 pjsua_var.player[i].port = NULL;
458 }
459 }
460
461 /* Destroy file recorders */
462 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
463 if (pjsua_var.recorder[i].port) {
464 pjmedia_port_destroy(pjsua_var.recorder[i].port);
465 pjsua_var.recorder[i].port = NULL;
466 }
467 }
468
469 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000470 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000471 if (pjsua_var.calls[i].med_tp) {
472 (*pjsua_var.calls[i].med_tp->op->destroy)(pjsua_var.calls[i].med_tp);
473 pjsua_var.calls[i].med_tp = NULL;
474 }
475 }
476
477 /* Destroy media endpoint. */
478 if (pjsua_var.med_endpt) {
479
480 /* Shutdown all codecs: */
481# if PJMEDIA_HAS_SPEEX_CODEC
482 pjmedia_codec_speex_deinit();
483# endif /* PJMEDIA_HAS_SPEEX_CODEC */
484
485# if PJMEDIA_HAS_GSM_CODEC
486 pjmedia_codec_gsm_deinit();
487# endif /* PJMEDIA_HAS_GSM_CODEC */
488
489# if PJMEDIA_HAS_G711_CODEC
490 pjmedia_codec_g711_deinit();
491# endif /* PJMEDIA_HAS_G711_CODEC */
492
493# if PJMEDIA_HAS_L16_CODEC
494 pjmedia_codec_l16_deinit();
495# endif /* PJMEDIA_HAS_L16_CODEC */
496
497
498 pjmedia_endpt_destroy(pjsua_var.med_endpt);
499 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000500
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000501 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000502 // Not necessary, as pjmedia_snd_deinit() should have been called
503 // in pjmedia_endpt_destroy().
504 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000505 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000506
Benny Prijonode479562007-03-15 10:23:55 +0000507 /* Reset RTP port */
508 next_rtp_port = 0;
509
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000510 return PJ_SUCCESS;
511}
512
513
Benny Prijonoc97608e2007-03-23 16:34:20 +0000514/* Create normal UDP media transports */
515static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000516{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000517 unsigned i;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000518 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000519 pj_status_t status;
520
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000521 /* Create each media transport */
522 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
523
Benny Prijono617c5bc2007-04-02 19:51:21 +0000524 status = create_rtp_rtcp_sock(cfg, &skinfo);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000525 if (status != PJ_SUCCESS) {
526 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
527 status);
528 goto on_error;
529 }
530 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000531 &skinfo, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000532 &pjsua_var.calls[i].med_tp);
533 if (status != PJ_SUCCESS) {
534 pjsua_perror(THIS_FILE, "Unable to create media transport",
535 status);
536 goto on_error;
537 }
Benny Prijono00cae612006-07-31 15:19:36 +0000538
539 pjmedia_transport_udp_simulate_lost(pjsua_var.calls[i].med_tp,
540 PJMEDIA_DIR_ENCODING,
541 pjsua_var.media_cfg.tx_drop_pct);
542
543 pjmedia_transport_udp_simulate_lost(pjsua_var.calls[i].med_tp,
544 PJMEDIA_DIR_DECODING,
545 pjsua_var.media_cfg.rx_drop_pct);
546
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000547 }
548
Benny Prijonoc97608e2007-03-23 16:34:20 +0000549 return PJ_SUCCESS;
550
551on_error:
552 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
553 if (pjsua_var.calls[i].med_tp != NULL) {
554 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
555 pjsua_var.calls[i].med_tp = NULL;
556 }
557 }
558
559 return status;
560}
561
562
Benny Prijono096c56c2007-09-15 08:30:16 +0000563/* This callback is called when ICE negotiation completes */
564static void on_ice_complete(pjmedia_transport *tp, pj_status_t result)
565{
566 unsigned id, c;
567 pj_bool_t found = PJ_FALSE;
568
569 /* We're only interested with failure case */
570 if (result == PJ_SUCCESS)
571 return;
572
573 /* Find call which has this media transport */
574
575 PJSUA_LOCK();
576
577 for (id=0, c=0; id<PJSUA_MAX_CALLS && c<pjsua_var.call_cnt; ++id) {
578 pjsua_call *call = &pjsua_var.calls[id];
579 if (call->inv) {
580 ++c;
581
582 if (call->med_tp == tp) {
583 call->media_st = PJSUA_CALL_MEDIA_ERROR;
584 call->media_dir = PJMEDIA_DIR_NONE;
585 found = PJ_TRUE;
586 break;
587 }
588 }
589 }
590
591 PJSUA_UNLOCK();
592
593 if (found && pjsua_var.ua_cfg.cb.on_call_media_state) {
594 pjsua_var.ua_cfg.cb.on_call_media_state(id);
595 }
596}
597
598
Benny Prijonoc97608e2007-03-23 16:34:20 +0000599/* Create ICE media transports (when ice is enabled) */
600static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
601{
602 unsigned i;
Benny Prijonob681a2f2007-03-25 18:44:51 +0000603 pj_sockaddr_in addr;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000604 pj_status_t status;
605
Benny Prijonoda9785b2007-04-02 20:43:06 +0000606 /* Make sure STUN server resolution has completed */
607 status = pjsua_resolve_stun_server(PJ_TRUE);
608 if (status != PJ_SUCCESS) {
609 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
610 return status;
611 }
612
Benny Prijonob681a2f2007-03-25 18:44:51 +0000613 pj_sockaddr_in_init(&addr, 0, (pj_uint16_t)cfg->port);
614
Benny Prijonoc97608e2007-03-23 16:34:20 +0000615 /* Create each media transport */
616 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijonoa6bd7582007-03-28 15:49:48 +0000617 pj_ice_strans_comp comp;
Benny Prijono096c56c2007-09-15 08:30:16 +0000618 pjmedia_ice_cb ice_cb;
Benny Prijonob681a2f2007-03-25 18:44:51 +0000619 int next_port;
Benny Prijonodc1fe222007-06-26 10:13:13 +0000620#if PJMEDIA_ADVERTISE_RTCP
621 enum { COMP_CNT=2 };
622#else
623 enum { COMP_CNT=1 };
624#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000625
Benny Prijono096c56c2007-09-15 08:30:16 +0000626 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
627 ice_cb.on_ice_complete = &on_ice_complete;
628
Benny Prijonodc1fe222007-06-26 10:13:13 +0000629 status = pjmedia_ice_create(pjsua_var.med_endpt, NULL, COMP_CNT,
Benny Prijono096c56c2007-09-15 08:30:16 +0000630 &pjsua_var.stun_cfg, &ice_cb,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000631 &pjsua_var.calls[i].med_tp);
632 if (status != PJ_SUCCESS) {
633 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
634 status);
635 goto on_error;
636 }
637
Benny Prijono11da9bc2007-09-15 08:55:00 +0000638 pjmedia_ice_simulate_lost(pjsua_var.calls[i].med_tp,
639 PJMEDIA_DIR_ENCODING,
640 pjsua_var.media_cfg.tx_drop_pct);
641
642 pjmedia_ice_simulate_lost(pjsua_var.calls[i].med_tp,
643 PJMEDIA_DIR_DECODING,
644 pjsua_var.media_cfg.rx_drop_pct);
645
Benny Prijonob681a2f2007-03-25 18:44:51 +0000646 status = pjmedia_ice_start_init(pjsua_var.calls[i].med_tp, 0, &addr,
647 &pjsua_var.stun_srv.ipv4, NULL);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000648 if (status != PJ_SUCCESS) {
Benny Prijonob681a2f2007-03-25 18:44:51 +0000649 pjsua_perror(THIS_FILE, "Error starting ICE transport",
Benny Prijonoc97608e2007-03-23 16:34:20 +0000650 status);
651 goto on_error;
652 }
653
Benny Prijonob681a2f2007-03-25 18:44:51 +0000654 pjmedia_ice_get_comp(pjsua_var.calls[i].med_tp, 1, &comp);
655 next_port = pj_ntohs(comp.local_addr.ipv4.sin_port);
656 next_port += 2;
657 addr.sin_port = pj_htons((pj_uint16_t)next_port);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000658 }
659
660 /* Wait until all ICE transports are ready */
661 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000662
663 /* Wait until interface status is PJ_SUCCESS */
664 for (;;) {
Benny Prijonob681a2f2007-03-25 18:44:51 +0000665 status = pjmedia_ice_get_init_status(pjsua_var.calls[i].med_tp);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000666 if (status == PJ_EPENDING)
667 pjsua_handle_events(100);
668 else
669 break;
670 }
671
672 if (status != PJ_SUCCESS) {
673 pjsua_perror(THIS_FILE,
674 "Error adding STUN address to ICE media transport",
675 status);
676 goto on_error;
677 }
678
Benny Prijonoc97608e2007-03-23 16:34:20 +0000679 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000680
681 return PJ_SUCCESS;
682
683on_error:
684 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
685 if (pjsua_var.calls[i].med_tp != NULL) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000686 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000687 pjsua_var.calls[i].med_tp = NULL;
688 }
689 }
690
Benny Prijonoc97608e2007-03-23 16:34:20 +0000691 return status;
692}
693
694
695/*
696 * Create UDP media transports for all the calls. This function creates
697 * one UDP media transport for each call.
698 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +0000699PJ_DEF(pj_status_t) pjsua_media_transports_create(
700 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000701{
702 pjsua_transport_config cfg;
703 unsigned i;
704 pj_status_t status;
705
706
707 /* Make sure pjsua_init() has been called */
708 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
709
710 PJSUA_LOCK();
711
712 /* Delete existing media transports */
713 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
714 if (pjsua_var.calls[i].med_tp != NULL) {
715 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
716 pjsua_var.calls[i].med_tp = NULL;
717 }
718 }
719
720 /* Copy config */
721 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
722
723 if (pjsua_var.media_cfg.enable_ice) {
724 status = create_ice_media_transports(&cfg);
725 } else {
726 status = create_udp_media_transports(&cfg);
727 }
728
729
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000730 PJSUA_UNLOCK();
731
732 return status;
733}
734
735
Benny Prijonoc97608e2007-03-23 16:34:20 +0000736pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
737 pjsip_role_e role)
738{
739 pjsua_call *call = &pjsua_var.calls[call_id];
740
741 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijonoa6bd7582007-03-28 15:49:48 +0000742 pj_ice_sess_role ice_role;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000743 pj_status_t status;
744
Benny Prijonoa6bd7582007-03-28 15:49:48 +0000745 ice_role = (role==PJSIP_ROLE_UAC ? PJ_ICE_SESS_ROLE_CONTROLLING :
746 PJ_ICE_SESS_ROLE_CONTROLLED);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000747
748 /* Restart ICE */
749 pjmedia_ice_stop_ice(call->med_tp);
750
751 status = pjmedia_ice_init_ice(call->med_tp, ice_role, NULL, NULL);
752 if (status != PJ_SUCCESS)
753 return status;
754 }
755
756 return PJ_SUCCESS;
757}
758
759pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
760 pj_pool_t *pool,
761 pjmedia_sdp_session **p_sdp)
762{
763 pjmedia_sdp_session *sdp;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000764 pjmedia_sock_info skinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000765 pjsua_call *call = &pjsua_var.calls[call_id];
766 pj_status_t status;
767
Benny Prijono55e82352007-05-10 20:49:08 +0000768 /* Return error if media transport has not been created yet
769 * (e.g. application is starting)
770 */
771 if (call->med_tp == NULL) {
772 return PJ_EBUSY;
773 }
774
Benny Prijono617c5bc2007-04-02 19:51:21 +0000775 /* Get media socket info */
776 pjmedia_transport_get_info(call->med_tp, &skinfo);
777
778 /* Create SDP */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000779 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, 1,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000780 &skinfo, &sdp);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000781 if (status != PJ_SUCCESS)
782 goto on_error;
783
Benny Prijono6ba8c542007-10-16 01:34:14 +0000784 /* Add NAT info in the SDP */
785 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
786 pjmedia_sdp_attr *a;
787 pj_str_t value;
788 char nat_info[80];
789
790 value.ptr = nat_info;
791 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
792 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
793 "%d", pjsua_var.nat_type);
794 } else {
795 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
796 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
797 "%d %s",
798 pjsua_var.nat_type,
799 type_name);
800 }
801
802 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
803
804 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
805
806 }
807
Benny Prijonoc97608e2007-03-23 16:34:20 +0000808 if (pjsua_var.media_cfg.enable_ice) {
809 status = pjmedia_ice_modify_sdp(call->med_tp, pool, sdp);
810 if (status != PJ_SUCCESS)
811 goto on_error;
812 }
813
814 *p_sdp = sdp;
815 return PJ_SUCCESS;
816
817on_error:
818 pjsua_media_channel_deinit(call_id);
819 return status;
820
821}
822
823
824static void stop_media_session(pjsua_call_id call_id)
825{
826 pjsua_call *call = &pjsua_var.calls[call_id];
827
828 if (call->conf_slot != PJSUA_INVALID_ID) {
829 pjmedia_conf_remove_port(pjsua_var.mconf, call->conf_slot);
830 call->conf_slot = PJSUA_INVALID_ID;
831 }
832
833 if (call->session) {
834 pjmedia_session_destroy(call->session);
835 call->session = NULL;
836
837 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
838 call_id));
839
840 }
841
842 call->media_st = PJSUA_CALL_MEDIA_NONE;
843}
844
845pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
846{
847 pjsua_call *call = &pjsua_var.calls[call_id];
848
849 stop_media_session(call_id);
850
851 if (pjsua_var.media_cfg.enable_ice) {
852 pjmedia_ice_stop_ice(call->med_tp);
853 }
854
855 return PJ_SUCCESS;
856}
857
858
859/*
860 * DTMF callback from the stream.
861 */
862static void dtmf_callback(pjmedia_stream *strm, void *user_data,
863 int digit)
864{
865 PJ_UNUSED_ARG(strm);
866
867 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
868 pjsua_call_id call_id;
869
870 call_id = (pjsua_call_id)user_data;
871 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
872 }
873}
874
875
876pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijonodbce2cf2007-03-28 16:24:00 +0000877 const pjmedia_sdp_session *local_sdp,
878 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000879{
880 int prev_media_st = 0;
881 pjsua_call *call = &pjsua_var.calls[call_id];
882 pjmedia_session_info sess_info;
883 pjmedia_port *media_port;
884 pj_str_t port_name;
885 char tmp[PJSIP_MAX_URL_SIZE];
886 pj_status_t status;
887
888 /* Destroy existing media session, if any. */
889 prev_media_st = call->media_st;
890 stop_media_session(call->index);
891
892 /* Create media session info based on SDP parameters.
893 * We only support one stream per session at the moment
894 */
895 status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
896 pjsua_var.med_endpt,
897 1,&sess_info,
898 local_sdp, remote_sdp);
899 if (status != PJ_SUCCESS)
900 return status;
901
902
903 /* Check if media is put on-hold */
904 if (sess_info.stream_cnt == 0 ||
905 sess_info.stream_info[0].dir == PJMEDIA_DIR_NONE)
906 {
907
908 /* Determine who puts the call on-hold */
909 if (prev_media_st == PJSUA_CALL_MEDIA_ACTIVE) {
910 if (pjmedia_sdp_neg_was_answer_remote(call->inv->neg)) {
911 /* It was local who offer hold */
912 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
913 } else {
914 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
915 }
916 }
917
918 call->media_dir = PJMEDIA_DIR_NONE;
919
Benny Prijono667952e2007-04-02 19:27:54 +0000920 /* Shutdown ICE session */
921 if (pjsua_var.media_cfg.enable_ice) {
922 pjmedia_ice_stop_ice(call->med_tp);
923 }
924
Benny Prijonoc97608e2007-03-23 16:34:20 +0000925 /* No need because we need keepalive? */
926
927 } else {
928
929 /* Start ICE */
930 if (pjsua_var.media_cfg.enable_ice) {
931 status = pjmedia_ice_start_ice(call->med_tp, call->inv->pool,
Benny Prijonofd70c9b2007-03-27 11:00:38 +0000932 remote_sdp, 0);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000933 if (status != PJ_SUCCESS)
934 return status;
935 }
936
937 /* Override ptime, if this option is specified. */
938 if (pjsua_var.media_cfg.ptime != 0) {
939 sess_info.stream_info[0].param->setting.frm_per_pkt = (pj_uint8_t)
940 (pjsua_var.media_cfg.ptime / sess_info.stream_info[0].param->info.frm_ptime);
941 if (sess_info.stream_info[0].param->setting.frm_per_pkt == 0)
942 sess_info.stream_info[0].param->setting.frm_per_pkt = 1;
943 }
944
945 /* Disable VAD, if this option is specified. */
946 if (pjsua_var.media_cfg.no_vad) {
947 sess_info.stream_info[0].param->setting.vad = 0;
948 }
949
950
951 /* Optionally, application may modify other stream settings here
952 * (such as jitter buffer parameters, codec ptime, etc.)
953 */
954 sess_info.stream_info[0].jb_init = pjsua_var.media_cfg.jb_init;
955 sess_info.stream_info[0].jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
956 sess_info.stream_info[0].jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
957 sess_info.stream_info[0].jb_max = pjsua_var.media_cfg.jb_max;
958
Benny Prijono8147f402007-11-21 14:50:07 +0000959 /* Set SSRC */
960 sess_info.stream_info[0].ssrc = call->ssrc;
961
Benny Prijonoc97608e2007-03-23 16:34:20 +0000962 /* Create session based on session info. */
963 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
964 &call->med_tp,
965 call, &call->session );
966 if (status != PJ_SUCCESS) {
967 return status;
968 }
969
970 /* If DTMF callback is installed by application, install our
971 * callback to the session.
972 */
973 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
974 pjmedia_session_set_dtmf_callback(call->session, 0,
975 &dtmf_callback,
976 (void*)(call->index));
977 }
978
979 /* Get the port interface of the first stream in the session.
980 * We need the port interface to add to the conference bridge.
981 */
982 pjmedia_session_get_port(call->session, 0, &media_port);
983
984
985 /*
986 * Add the call to conference bridge.
987 */
988 port_name.ptr = tmp;
989 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
990 call->inv->dlg->remote.info->uri,
991 tmp, sizeof(tmp));
992 if (port_name.slen < 1) {
993 port_name = pj_str("call");
994 }
995 status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
996 media_port,
997 &port_name,
998 (unsigned*)&call->conf_slot);
999 if (status != PJ_SUCCESS) {
1000 return status;
1001 }
1002
1003 /* Call's media state is active */
1004 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
1005 call->media_dir = sess_info.stream_info[0].dir;
1006 }
1007
1008 /* Print info. */
1009 {
1010 char info[80];
1011 int info_len = 0;
1012 unsigned i;
1013
1014 for (i=0; i<sess_info.stream_cnt; ++i) {
1015 int len;
1016 const char *dir;
1017 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1018
1019 switch (strm_info->dir) {
1020 case PJMEDIA_DIR_NONE:
1021 dir = "inactive";
1022 break;
1023 case PJMEDIA_DIR_ENCODING:
1024 dir = "sendonly";
1025 break;
1026 case PJMEDIA_DIR_DECODING:
1027 dir = "recvonly";
1028 break;
1029 case PJMEDIA_DIR_ENCODING_DECODING:
1030 dir = "sendrecv";
1031 break;
1032 default:
1033 dir = "unknown";
1034 break;
1035 }
1036 len = pj_ansi_sprintf( info+info_len,
1037 ", stream #%d: %.*s (%s)", i,
1038 (int)strm_info->fmt.encoding_name.slen,
1039 strm_info->fmt.encoding_name.ptr,
1040 dir);
1041 if (len > 0)
1042 info_len += len;
1043 }
1044 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1045 }
1046
1047 return PJ_SUCCESS;
1048}
1049
1050
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001051/*
1052 * Get maxinum number of conference ports.
1053 */
1054PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1055{
1056 return pjsua_var.media_cfg.max_media_ports;
1057}
1058
1059
1060/*
1061 * Get current number of active ports in the bridge.
1062 */
1063PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1064{
1065 unsigned ports[256];
1066 unsigned count = PJ_ARRAY_SIZE(ports);
1067 pj_status_t status;
1068
1069 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1070 if (status != PJ_SUCCESS)
1071 count = 0;
1072
1073 return count;
1074}
1075
1076
1077/*
1078 * Enumerate all conference ports.
1079 */
1080PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1081 unsigned *count)
1082{
1083 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1084}
1085
1086
1087/*
1088 * Get information about the specified conference port
1089 */
1090PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1091 pjsua_conf_port_info *info)
1092{
1093 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001094 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001095 pj_status_t status;
1096
1097 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1098 if (status != PJ_SUCCESS)
1099 return status;
1100
Benny Prijonoac623b32006-07-03 15:19:31 +00001101 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001102 info->slot_id = id;
1103 info->name = cinfo.name;
1104 info->clock_rate = cinfo.clock_rate;
1105 info->channel_count = cinfo.channel_count;
1106 info->samples_per_frame = cinfo.samples_per_frame;
1107 info->bits_per_sample = cinfo.bits_per_sample;
1108
1109 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001110 info->listener_cnt = cinfo.listener_cnt;
1111 for (i=0; i<cinfo.listener_cnt; ++i) {
1112 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001113 }
1114
1115 return PJ_SUCCESS;
1116}
1117
1118
1119/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001120 * Add arbitrary media port to PJSUA's conference bridge.
1121 */
1122PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1123 pjmedia_port *port,
1124 pjsua_conf_port_id *p_id)
1125{
1126 pj_status_t status;
1127
1128 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1129 port, NULL, (unsigned*)p_id);
1130 if (status != PJ_SUCCESS) {
1131 if (p_id)
1132 *p_id = PJSUA_INVALID_ID;
1133 }
1134
1135 return status;
1136}
1137
1138
1139/*
1140 * Remove arbitrary slot from the conference bridge.
1141 */
1142PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1143{
1144 return pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1145}
1146
1147
1148/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001149 * Establish unidirectional media flow from souce to sink.
1150 */
1151PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1152 pjsua_conf_port_id sink)
1153{
1154 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
1155}
1156
1157
1158/*
1159 * Disconnect media flow from the source to destination port.
1160 */
1161PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
1162 pjsua_conf_port_id sink)
1163{
1164 return pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
1165}
1166
1167
Benny Prijono6dd967c2006-12-26 02:27:14 +00001168/*
1169 * Adjust the signal level to be transmitted from the bridge to the
1170 * specified port by making it louder or quieter.
1171 */
1172PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
1173 float level)
1174{
1175 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
1176 (int)((level-1) * 128));
1177}
1178
1179/*
1180 * Adjust the signal level to be received from the specified port (to
1181 * the bridge) by making it louder or quieter.
1182 */
1183PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
1184 float level)
1185{
1186 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
1187 (int)((level-1) * 128));
1188}
1189
1190
1191/*
1192 * Get last signal level transmitted to or received from the specified port.
1193 */
1194PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
1195 unsigned *tx_level,
1196 unsigned *rx_level)
1197{
1198 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
1199 tx_level, rx_level);
1200}
1201
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001202/*****************************************************************************
1203 * File player.
1204 */
1205
Benny Prijonod5696da2007-07-17 16:25:45 +00001206static char* get_basename(const char *path, unsigned len)
1207{
1208 char *p = ((char*)path) + len;
1209
1210 if (len==0)
1211 return p;
1212
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001213 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00001214
1215 return (p==path) ? p : p+1;
1216}
1217
1218
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001219/*
1220 * Create a file player, and automatically connect this player to
1221 * the conference bridge.
1222 */
1223PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1224 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001225 pjsua_player_id *p_id)
1226{
1227 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001228 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00001229 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001230 pjmedia_port *port;
1231 pj_status_t status;
1232
1233 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1234 return PJ_ETOOMANY;
1235
1236 PJSUA_LOCK();
1237
1238 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1239 if (pjsua_var.player[file_id].port == NULL)
1240 break;
1241 }
1242
1243 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1244 /* This is unexpected */
1245 PJSUA_UNLOCK();
1246 pj_assert(0);
1247 return PJ_EBUG;
1248 }
1249
1250 pj_memcpy(path, filename->ptr, filename->slen);
1251 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00001252
1253 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1254 if (!pool) {
1255 PJSUA_UNLOCK();
1256 return PJ_ENOMEM;
1257 }
1258
1259 status = pjmedia_wav_player_port_create(pool, path,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001260 pjsua_var.mconf_cfg.samples_per_frame *
1261 1000 / pjsua_var.media_cfg.clock_rate,
Benny Prijono00cae612006-07-31 15:19:36 +00001262 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001263 if (status != PJ_SUCCESS) {
1264 PJSUA_UNLOCK();
1265 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001266 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001267 return status;
1268 }
1269
1270 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
1271 port, filename, &slot);
1272 if (status != PJ_SUCCESS) {
1273 pjmedia_port_destroy(port);
1274 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00001275 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1276 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001277 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001278 return status;
1279 }
1280
Benny Prijonoa66c3312007-01-21 23:12:40 +00001281 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00001282 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001283 pjsua_var.player[file_id].port = port;
1284 pjsua_var.player[file_id].slot = slot;
1285
1286 if (p_id) *p_id = file_id;
1287
1288 ++pjsua_var.player_cnt;
1289
1290 PJSUA_UNLOCK();
1291 return PJ_SUCCESS;
1292}
1293
1294
1295/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00001296 * Create a file playlist media port, and automatically add the port
1297 * to the conference bridge.
1298 */
1299PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
1300 unsigned file_count,
1301 const pj_str_t *label,
1302 unsigned options,
1303 pjsua_player_id *p_id)
1304{
1305 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00001306 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001307 pjmedia_port *port;
1308 pj_status_t status;
1309
1310 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1311 return PJ_ETOOMANY;
1312
1313 PJSUA_LOCK();
1314
1315 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1316 if (pjsua_var.player[file_id].port == NULL)
1317 break;
1318 }
1319
1320 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1321 /* This is unexpected */
1322 PJSUA_UNLOCK();
1323 pj_assert(0);
1324 return PJ_EBUG;
1325 }
1326
1327
1328 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
1329 pjsua_var.media_cfg.clock_rate;
1330
Benny Prijonod5696da2007-07-17 16:25:45 +00001331 pool = pjsua_pool_create("playlist", 1000, 1000);
1332 if (!pool) {
1333 PJSUA_UNLOCK();
1334 return PJ_ENOMEM;
1335 }
1336
1337 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001338 file_names, file_count,
1339 ptime, options, 0, &port);
1340 if (status != PJ_SUCCESS) {
1341 PJSUA_UNLOCK();
1342 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001343 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001344 return status;
1345 }
1346
Benny Prijonod5696da2007-07-17 16:25:45 +00001347 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001348 port, &port->info.name, &slot);
1349 if (status != PJ_SUCCESS) {
1350 pjmedia_port_destroy(port);
1351 PJSUA_UNLOCK();
1352 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001353 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001354 return status;
1355 }
1356
1357 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00001358 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001359 pjsua_var.player[file_id].port = port;
1360 pjsua_var.player[file_id].slot = slot;
1361
1362 if (p_id) *p_id = file_id;
1363
1364 ++pjsua_var.player_cnt;
1365
1366 PJSUA_UNLOCK();
1367 return PJ_SUCCESS;
1368
1369}
1370
1371
1372/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001373 * Get conference port ID associated with player.
1374 */
1375PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
1376{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001377 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001378 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1379
1380 return pjsua_var.player[id].slot;
1381}
1382
Benny Prijono469b1522006-12-26 03:05:17 +00001383/*
1384 * Get the media port for the player.
1385 */
1386PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_recorder_id id,
1387 pjmedia_port **p_port)
1388{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001389 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001390 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1391 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1392
1393 *p_port = pjsua_var.player[id].port;
1394
1395 return PJ_SUCCESS;
1396}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001397
1398/*
1399 * Set playback position.
1400 */
1401PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
1402 pj_uint32_t samples)
1403{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001404 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001405 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001406 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001407
1408 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
1409}
1410
1411
1412/*
1413 * Close the file, remove the player from the bridge, and free
1414 * resources associated with the file player.
1415 */
1416PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
1417{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001418 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001419 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1420
1421 PJSUA_LOCK();
1422
1423 if (pjsua_var.player[id].port) {
1424 pjmedia_conf_remove_port(pjsua_var.mconf,
1425 pjsua_var.player[id].slot);
1426 pjmedia_port_destroy(pjsua_var.player[id].port);
1427 pjsua_var.player[id].port = NULL;
1428 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00001429 pj_pool_release(pjsua_var.player[id].pool);
1430 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001431 pjsua_var.player_cnt--;
1432 }
1433
1434 PJSUA_UNLOCK();
1435
1436 return PJ_SUCCESS;
1437}
1438
1439
1440/*****************************************************************************
1441 * File recorder.
1442 */
1443
1444/*
1445 * Create a file recorder, and automatically connect this recorder to
1446 * the conference bridge.
1447 */
1448PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00001449 unsigned enc_type,
1450 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001451 pj_ssize_t max_size,
1452 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001453 pjsua_recorder_id *p_id)
1454{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001455 enum Format
1456 {
1457 FMT_UNKNOWN,
1458 FMT_WAV,
1459 FMT_MP3,
1460 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001461 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001462 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001463 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00001464 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00001465 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001466 pjmedia_port *port;
1467 pj_status_t status;
1468
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001469 /* Filename must present */
1470 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
1471
Benny Prijono00cae612006-07-31 15:19:36 +00001472 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001473 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001474
Benny Prijono8f310522006-10-20 11:08:49 +00001475 /* Don't support encoding type at present */
1476 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001477
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001478 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
1479 return PJ_ETOOMANY;
1480
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001481 /* Determine the file format */
1482 ext.ptr = filename->ptr + filename->slen - 4;
1483 ext.slen = 4;
1484
1485 if (pj_stricmp2(&ext, ".wav") == 0)
1486 file_format = FMT_WAV;
1487 else if (pj_stricmp2(&ext, ".mp3") == 0)
1488 file_format = FMT_MP3;
1489 else {
1490 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
1491 "determine file format for %.*s",
1492 (int)filename->slen, filename->ptr));
1493 return PJ_ENOTSUP;
1494 }
1495
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001496 PJSUA_LOCK();
1497
1498 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
1499 if (pjsua_var.recorder[file_id].port == NULL)
1500 break;
1501 }
1502
1503 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
1504 /* This is unexpected */
1505 PJSUA_UNLOCK();
1506 pj_assert(0);
1507 return PJ_EBUG;
1508 }
1509
1510 pj_memcpy(path, filename->ptr, filename->slen);
1511 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001512
Benny Prijonod5696da2007-07-17 16:25:45 +00001513 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1514 if (!pool) {
1515 PJSUA_UNLOCK();
1516 return PJ_ENOMEM;
1517 }
1518
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001519 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00001520 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001521 pjsua_var.media_cfg.clock_rate,
1522 pjsua_var.mconf_cfg.channel_count,
1523 pjsua_var.mconf_cfg.samples_per_frame,
1524 pjsua_var.mconf_cfg.bits_per_sample,
1525 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001526 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00001527 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001528 port = NULL;
1529 status = PJ_ENOTSUP;
1530 }
1531
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001532 if (status != PJ_SUCCESS) {
1533 PJSUA_UNLOCK();
1534 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001535 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001536 return status;
1537 }
1538
Benny Prijonod5696da2007-07-17 16:25:45 +00001539 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001540 port, filename, &slot);
1541 if (status != PJ_SUCCESS) {
1542 pjmedia_port_destroy(port);
1543 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00001544 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001545 return status;
1546 }
1547
1548 pjsua_var.recorder[file_id].port = port;
1549 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00001550 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001551
1552 if (p_id) *p_id = file_id;
1553
1554 ++pjsua_var.rec_cnt;
1555
1556 PJSUA_UNLOCK();
1557 return PJ_SUCCESS;
1558}
1559
1560
1561/*
1562 * Get conference port associated with recorder.
1563 */
1564PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
1565{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001566 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1567 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001568 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1569
1570 return pjsua_var.recorder[id].slot;
1571}
1572
Benny Prijono469b1522006-12-26 03:05:17 +00001573/*
1574 * Get the media port for the recorder.
1575 */
1576PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
1577 pjmedia_port **p_port)
1578{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001579 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1580 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001581 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1582 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1583
1584 *p_port = pjsua_var.recorder[id].port;
1585 return PJ_SUCCESS;
1586}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001587
1588/*
1589 * Destroy recorder (this will complete recording).
1590 */
1591PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
1592{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001593 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1594 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001595 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1596
1597 PJSUA_LOCK();
1598
1599 if (pjsua_var.recorder[id].port) {
1600 pjmedia_conf_remove_port(pjsua_var.mconf,
1601 pjsua_var.recorder[id].slot);
1602 pjmedia_port_destroy(pjsua_var.recorder[id].port);
1603 pjsua_var.recorder[id].port = NULL;
1604 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00001605 pj_pool_release(pjsua_var.recorder[id].pool);
1606 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001607 pjsua_var.rec_cnt--;
1608 }
1609
1610 PJSUA_UNLOCK();
1611
1612 return PJ_SUCCESS;
1613}
1614
1615
1616/*****************************************************************************
1617 * Sound devices.
1618 */
1619
1620/*
1621 * Enum sound devices.
1622 */
1623PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
1624 unsigned *count)
1625{
1626 unsigned i, dev_count;
1627
1628 dev_count = pjmedia_snd_get_dev_count();
1629
1630 if (dev_count > *count) dev_count = *count;
1631
1632 for (i=0; i<dev_count; ++i) {
1633 const pjmedia_snd_dev_info *ci;
1634
1635 ci = pjmedia_snd_get_dev_info(i);
1636 pj_memcpy(&info[i], ci, sizeof(*ci));
1637 }
1638
1639 *count = dev_count;
1640
1641 return PJ_SUCCESS;
1642}
1643
1644
1645/* Close existing sound device */
1646static void close_snd_dev(void)
1647{
1648 /* Close sound device */
1649 if (pjsua_var.snd_port) {
1650 const pjmedia_snd_dev_info *cap_info, *play_info;
1651
1652 cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
1653 play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
1654
1655 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
1656 "%s sound capture device",
1657 play_info->name, cap_info->name));
1658
1659 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
1660 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1661 pjsua_var.snd_port = NULL;
1662 }
1663
1664 /* Close null sound device */
1665 if (pjsua_var.null_snd) {
1666 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
1667 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
1668 pjsua_var.null_snd = NULL;
1669 }
1670}
1671
1672/*
1673 * Select or change sound device. Application may call this function at
1674 * any time to replace current sound device.
1675 */
1676PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
1677 int playback_dev)
1678{
1679 pjmedia_port *conf_port;
Benny Prijono6dd967c2006-12-26 02:27:14 +00001680 const pjmedia_snd_dev_info *play_info;
Benny Prijono26056d82006-10-11 16:03:41 +00001681 unsigned clock_rates[] = { 0, 22050, 44100, 48000, 11025, 32000, 8000};
Benny Prijono658a1c52006-10-11 21:56:16 +00001682 unsigned selected_clock_rate = 0;
Benny Prijono26056d82006-10-11 16:03:41 +00001683 unsigned i;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00001684 pjmedia_snd_stream *strm;
1685 pjmedia_snd_stream_info si;
1686 pj_str_t tmp;
Benny Prijono26056d82006-10-11 16:03:41 +00001687 pj_status_t status = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001688
1689 /* Close existing sound port */
1690 close_snd_dev();
1691
1692
Benny Prijono26056d82006-10-11 16:03:41 +00001693 /* Set default clock rate */
1694 clock_rates[0] = pjsua_var.media_cfg.clock_rate;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001695
Benny Prijono26056d82006-10-11 16:03:41 +00001696 /* Attempts to open the sound device with different clock rates */
1697 for (i=0; i<PJ_ARRAY_SIZE(clock_rates); ++i) {
1698 char errmsg[PJ_ERR_MSG_SIZE];
Benny Prijonocf0b4b22007-10-06 17:31:09 +00001699 unsigned fps;
Benny Prijono26056d82006-10-11 16:03:41 +00001700
1701 PJ_LOG(4,(THIS_FILE,
1702 "pjsua_set_snd_dev(): attempting to open devices "
1703 "@%d Hz", clock_rates[i]));
1704
1705 /* Create the sound device. Sound port will start immediately. */
Benny Prijonocf0b4b22007-10-06 17:31:09 +00001706 fps = 1000 / pjsua_var.media_cfg.audio_frame_ptime;
Benny Prijono26056d82006-10-11 16:03:41 +00001707 status = pjmedia_snd_port_create(pjsua_var.pool, capture_dev,
1708 playback_dev,
1709 clock_rates[i], 1,
Benny Prijonocf0b4b22007-10-06 17:31:09 +00001710 clock_rates[i]/fps,
Benny Prijono26056d82006-10-11 16:03:41 +00001711 16, 0, &pjsua_var.snd_port);
1712
Benny Prijono658a1c52006-10-11 21:56:16 +00001713 if (status == PJ_SUCCESS) {
1714 selected_clock_rate = clock_rates[i];
Benny Prijono26056d82006-10-11 16:03:41 +00001715 break;
Benny Prijono658a1c52006-10-11 21:56:16 +00001716 }
Benny Prijono26056d82006-10-11 16:03:41 +00001717
1718 pj_strerror(status, errmsg, sizeof(errmsg));
Benny Prijono658a1c52006-10-11 21:56:16 +00001719 PJ_LOG(4, (THIS_FILE, "..failed: %s", errmsg));
Benny Prijono26056d82006-10-11 16:03:41 +00001720 }
1721
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001722 if (status != PJ_SUCCESS) {
1723 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
1724 return status;
1725 }
1726
1727 /* Get the port0 of the conference bridge. */
1728 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1729 pj_assert(conf_port != NULL);
1730
Benny Prijonof20687a2006-08-04 18:27:19 +00001731 /* Set AEC */
Benny Prijono5da50432006-08-07 10:24:52 +00001732 pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
1733 pjsua_var.media_cfg.ec_tail_len,
1734 pjsua_var.media_cfg.ec_options);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001735
Benny Prijono658a1c52006-10-11 21:56:16 +00001736 /* If there's mismatch between sound port and conference's port,
1737 * create a resample port to bridge them.
1738 */
1739 if (selected_clock_rate != pjsua_var.media_cfg.clock_rate) {
1740 pjmedia_port *resample_port;
1741
1742 status = pjmedia_resample_port_create(pjsua_var.pool, conf_port,
1743 selected_clock_rate, 0,
1744 &resample_port);
1745 if (status != PJ_SUCCESS) {
1746 pjsua_perror("Error creating resample port", THIS_FILE, status);
1747 return status;
1748 }
1749
1750 conf_port = resample_port;
1751 }
1752
Benny Prijono52a93912006-08-04 20:54:37 +00001753 /* Connect sound port to the bridge */
1754 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
1755 conf_port );
1756 if (status != PJ_SUCCESS) {
1757 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
1758 "sound device", status);
1759 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1760 pjsua_var.snd_port = NULL;
1761 return status;
1762 }
1763
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001764 /* Save the device IDs */
1765 pjsua_var.cap_dev = capture_dev;
1766 pjsua_var.play_dev = playback_dev;
1767
Benny Prijonoc53c6d72006-11-27 09:54:03 +00001768 /* Update sound device name. */
1769 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
1770 pjmedia_snd_stream_get_info(strm, &si);
1771 play_info = pjmedia_snd_get_dev_info(si.rec_id);
1772
1773 pjmedia_conf_set_port0_name(pjsua_var.mconf,
1774 pj_cstr(&tmp, play_info->name));
1775
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001776 return PJ_SUCCESS;
1777}
1778
1779
1780/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00001781 * Get currently active sound devices. If sound devices has not been created
1782 * (for example when pjsua_start() is not called), it is possible that
1783 * the function returns PJ_SUCCESS with -1 as device IDs.
1784 */
1785PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
1786 int *playback_dev)
1787{
1788 if (capture_dev) {
1789 *capture_dev = pjsua_var.cap_dev;
1790 }
1791 if (playback_dev) {
1792 *playback_dev = pjsua_var.play_dev;
1793 }
1794
1795 return PJ_SUCCESS;
1796}
1797
1798
1799/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001800 * Use null sound device.
1801 */
1802PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
1803{
1804 pjmedia_port *conf_port;
1805 pj_status_t status;
1806
1807 /* Close existing sound device */
1808 close_snd_dev();
1809
1810 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
1811
1812 /* Get the port0 of the conference bridge. */
1813 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1814 pj_assert(conf_port != NULL);
1815
1816 /* Create master port, connecting port0 of the conference bridge to
1817 * a null port.
1818 */
1819 status = pjmedia_master_port_create(pjsua_var.pool, pjsua_var.null_port,
1820 conf_port, 0, &pjsua_var.null_snd);
1821 if (status != PJ_SUCCESS) {
1822 pjsua_perror(THIS_FILE, "Unable to create null sound device",
1823 status);
1824 return status;
1825 }
1826
1827 /* Start the master port */
1828 status = pjmedia_master_port_start(pjsua_var.null_snd);
1829 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
1830
1831 return PJ_SUCCESS;
1832}
1833
1834
Benny Prijonoe909eac2006-07-27 22:04:56 +00001835
1836/*
1837 * Use no device!
1838 */
1839PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
1840{
1841 /* Close existing sound device */
1842 close_snd_dev();
1843
1844 pjsua_var.no_snd = PJ_TRUE;
1845 return pjmedia_conf_get_master_port(pjsua_var.mconf);
1846}
1847
1848
Benny Prijonof20687a2006-08-04 18:27:19 +00001849/*
1850 * Configure the AEC settings of the sound port.
1851 */
Benny Prijono5da50432006-08-07 10:24:52 +00001852PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00001853{
1854 pjsua_var.media_cfg.ec_tail_len = tail_ms;
1855
1856 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00001857 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
1858 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00001859
1860 return PJ_SUCCESS;
1861}
1862
1863
1864/*
1865 * Get current AEC tail length.
1866 */
Benny Prijono22dfe592006-08-06 12:07:13 +00001867PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00001868{
1869 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
1870 return PJ_SUCCESS;
1871}
1872
Benny Prijonoe909eac2006-07-27 22:04:56 +00001873
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001874/*****************************************************************************
1875 * Codecs.
1876 */
1877
1878/*
1879 * Enum all supported codecs in the system.
1880 */
1881PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
1882 unsigned *p_count )
1883{
1884 pjmedia_codec_mgr *codec_mgr;
1885 pjmedia_codec_info info[32];
1886 unsigned i, count, prio[32];
1887 pj_status_t status;
1888
1889 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1890 count = PJ_ARRAY_SIZE(info);
1891 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
1892 if (status != PJ_SUCCESS) {
1893 *p_count = 0;
1894 return status;
1895 }
1896
1897 if (count > *p_count) count = *p_count;
1898
1899 for (i=0; i<count; ++i) {
1900 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
1901 id[i].codec_id = pj_str(id[i].buf_);
1902 id[i].priority = (pj_uint8_t) prio[i];
1903 }
1904
1905 *p_count = count;
1906
1907 return PJ_SUCCESS;
1908}
1909
1910
1911/*
1912 * Change codec priority.
1913 */
1914PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
1915 pj_uint8_t priority )
1916{
1917 pjmedia_codec_mgr *codec_mgr;
1918
1919 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1920
1921 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
1922 priority);
1923}
1924
1925
1926/*
1927 * Get codec parameters.
1928 */
1929PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
1930 pjmedia_codec_param *param )
1931{
1932 const pjmedia_codec_info *info;
1933 pjmedia_codec_mgr *codec_mgr;
1934 unsigned count = 1;
1935 pj_status_t status;
1936
1937 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1938
1939 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
1940 &count, &info, NULL);
1941 if (status != PJ_SUCCESS)
1942 return status;
1943
1944 if (count != 1)
1945 return PJ_ENOTFOUND;
1946
1947 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
1948 return status;
1949}
1950
1951
1952/*
1953 * Set codec parameters.
1954 */
1955PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
1956 const pjmedia_codec_param *param)
1957{
Benny Prijono00cae612006-07-31 15:19:36 +00001958 PJ_UNUSED_ARG(id);
1959 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001960 PJ_TODO(set_codec_param);
1961 return PJ_SUCCESS;
1962}