blob: 5fa16462e4e6adb792528dbb42a11af579cc8325 [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
959 /* Create session based on session info. */
960 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
961 &call->med_tp,
962 call, &call->session );
963 if (status != PJ_SUCCESS) {
964 return status;
965 }
966
967 /* If DTMF callback is installed by application, install our
968 * callback to the session.
969 */
970 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
971 pjmedia_session_set_dtmf_callback(call->session, 0,
972 &dtmf_callback,
973 (void*)(call->index));
974 }
975
976 /* Get the port interface of the first stream in the session.
977 * We need the port interface to add to the conference bridge.
978 */
979 pjmedia_session_get_port(call->session, 0, &media_port);
980
981
982 /*
983 * Add the call to conference bridge.
984 */
985 port_name.ptr = tmp;
986 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
987 call->inv->dlg->remote.info->uri,
988 tmp, sizeof(tmp));
989 if (port_name.slen < 1) {
990 port_name = pj_str("call");
991 }
992 status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
993 media_port,
994 &port_name,
995 (unsigned*)&call->conf_slot);
996 if (status != PJ_SUCCESS) {
997 return status;
998 }
999
1000 /* Call's media state is active */
1001 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
1002 call->media_dir = sess_info.stream_info[0].dir;
1003 }
1004
1005 /* Print info. */
1006 {
1007 char info[80];
1008 int info_len = 0;
1009 unsigned i;
1010
1011 for (i=0; i<sess_info.stream_cnt; ++i) {
1012 int len;
1013 const char *dir;
1014 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1015
1016 switch (strm_info->dir) {
1017 case PJMEDIA_DIR_NONE:
1018 dir = "inactive";
1019 break;
1020 case PJMEDIA_DIR_ENCODING:
1021 dir = "sendonly";
1022 break;
1023 case PJMEDIA_DIR_DECODING:
1024 dir = "recvonly";
1025 break;
1026 case PJMEDIA_DIR_ENCODING_DECODING:
1027 dir = "sendrecv";
1028 break;
1029 default:
1030 dir = "unknown";
1031 break;
1032 }
1033 len = pj_ansi_sprintf( info+info_len,
1034 ", stream #%d: %.*s (%s)", i,
1035 (int)strm_info->fmt.encoding_name.slen,
1036 strm_info->fmt.encoding_name.ptr,
1037 dir);
1038 if (len > 0)
1039 info_len += len;
1040 }
1041 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1042 }
1043
1044 return PJ_SUCCESS;
1045}
1046
1047
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001048/*
1049 * Get maxinum number of conference ports.
1050 */
1051PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1052{
1053 return pjsua_var.media_cfg.max_media_ports;
1054}
1055
1056
1057/*
1058 * Get current number of active ports in the bridge.
1059 */
1060PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1061{
1062 unsigned ports[256];
1063 unsigned count = PJ_ARRAY_SIZE(ports);
1064 pj_status_t status;
1065
1066 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1067 if (status != PJ_SUCCESS)
1068 count = 0;
1069
1070 return count;
1071}
1072
1073
1074/*
1075 * Enumerate all conference ports.
1076 */
1077PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1078 unsigned *count)
1079{
1080 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1081}
1082
1083
1084/*
1085 * Get information about the specified conference port
1086 */
1087PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1088 pjsua_conf_port_info *info)
1089{
1090 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001091 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001092 pj_status_t status;
1093
1094 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1095 if (status != PJ_SUCCESS)
1096 return status;
1097
Benny Prijonoac623b32006-07-03 15:19:31 +00001098 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001099 info->slot_id = id;
1100 info->name = cinfo.name;
1101 info->clock_rate = cinfo.clock_rate;
1102 info->channel_count = cinfo.channel_count;
1103 info->samples_per_frame = cinfo.samples_per_frame;
1104 info->bits_per_sample = cinfo.bits_per_sample;
1105
1106 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001107 info->listener_cnt = cinfo.listener_cnt;
1108 for (i=0; i<cinfo.listener_cnt; ++i) {
1109 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001110 }
1111
1112 return PJ_SUCCESS;
1113}
1114
1115
1116/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001117 * Add arbitrary media port to PJSUA's conference bridge.
1118 */
1119PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1120 pjmedia_port *port,
1121 pjsua_conf_port_id *p_id)
1122{
1123 pj_status_t status;
1124
1125 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1126 port, NULL, (unsigned*)p_id);
1127 if (status != PJ_SUCCESS) {
1128 if (p_id)
1129 *p_id = PJSUA_INVALID_ID;
1130 }
1131
1132 return status;
1133}
1134
1135
1136/*
1137 * Remove arbitrary slot from the conference bridge.
1138 */
1139PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1140{
1141 return pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1142}
1143
1144
1145/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001146 * Establish unidirectional media flow from souce to sink.
1147 */
1148PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1149 pjsua_conf_port_id sink)
1150{
1151 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
1152}
1153
1154
1155/*
1156 * Disconnect media flow from the source to destination port.
1157 */
1158PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
1159 pjsua_conf_port_id sink)
1160{
1161 return pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
1162}
1163
1164
Benny Prijono6dd967c2006-12-26 02:27:14 +00001165/*
1166 * Adjust the signal level to be transmitted from the bridge to the
1167 * specified port by making it louder or quieter.
1168 */
1169PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
1170 float level)
1171{
1172 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
1173 (int)((level-1) * 128));
1174}
1175
1176/*
1177 * Adjust the signal level to be received from the specified port (to
1178 * the bridge) by making it louder or quieter.
1179 */
1180PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
1181 float level)
1182{
1183 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
1184 (int)((level-1) * 128));
1185}
1186
1187
1188/*
1189 * Get last signal level transmitted to or received from the specified port.
1190 */
1191PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
1192 unsigned *tx_level,
1193 unsigned *rx_level)
1194{
1195 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
1196 tx_level, rx_level);
1197}
1198
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001199/*****************************************************************************
1200 * File player.
1201 */
1202
Benny Prijonod5696da2007-07-17 16:25:45 +00001203static char* get_basename(const char *path, unsigned len)
1204{
1205 char *p = ((char*)path) + len;
1206
1207 if (len==0)
1208 return p;
1209
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001210 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00001211
1212 return (p==path) ? p : p+1;
1213}
1214
1215
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001216/*
1217 * Create a file player, and automatically connect this player to
1218 * the conference bridge.
1219 */
1220PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1221 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001222 pjsua_player_id *p_id)
1223{
1224 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001225 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00001226 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001227 pjmedia_port *port;
1228 pj_status_t status;
1229
1230 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1231 return PJ_ETOOMANY;
1232
1233 PJSUA_LOCK();
1234
1235 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1236 if (pjsua_var.player[file_id].port == NULL)
1237 break;
1238 }
1239
1240 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1241 /* This is unexpected */
1242 PJSUA_UNLOCK();
1243 pj_assert(0);
1244 return PJ_EBUG;
1245 }
1246
1247 pj_memcpy(path, filename->ptr, filename->slen);
1248 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00001249
1250 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1251 if (!pool) {
1252 PJSUA_UNLOCK();
1253 return PJ_ENOMEM;
1254 }
1255
1256 status = pjmedia_wav_player_port_create(pool, path,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001257 pjsua_var.mconf_cfg.samples_per_frame *
1258 1000 / pjsua_var.media_cfg.clock_rate,
Benny Prijono00cae612006-07-31 15:19:36 +00001259 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001260 if (status != PJ_SUCCESS) {
1261 PJSUA_UNLOCK();
1262 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001263 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001264 return status;
1265 }
1266
1267 status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool,
1268 port, filename, &slot);
1269 if (status != PJ_SUCCESS) {
1270 pjmedia_port_destroy(port);
1271 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00001272 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1273 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001274 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001275 return status;
1276 }
1277
Benny Prijonoa66c3312007-01-21 23:12:40 +00001278 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00001279 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001280 pjsua_var.player[file_id].port = port;
1281 pjsua_var.player[file_id].slot = slot;
1282
1283 if (p_id) *p_id = file_id;
1284
1285 ++pjsua_var.player_cnt;
1286
1287 PJSUA_UNLOCK();
1288 return PJ_SUCCESS;
1289}
1290
1291
1292/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00001293 * Create a file playlist media port, and automatically add the port
1294 * to the conference bridge.
1295 */
1296PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
1297 unsigned file_count,
1298 const pj_str_t *label,
1299 unsigned options,
1300 pjsua_player_id *p_id)
1301{
1302 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00001303 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001304 pjmedia_port *port;
1305 pj_status_t status;
1306
1307 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1308 return PJ_ETOOMANY;
1309
1310 PJSUA_LOCK();
1311
1312 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1313 if (pjsua_var.player[file_id].port == NULL)
1314 break;
1315 }
1316
1317 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1318 /* This is unexpected */
1319 PJSUA_UNLOCK();
1320 pj_assert(0);
1321 return PJ_EBUG;
1322 }
1323
1324
1325 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
1326 pjsua_var.media_cfg.clock_rate;
1327
Benny Prijonod5696da2007-07-17 16:25:45 +00001328 pool = pjsua_pool_create("playlist", 1000, 1000);
1329 if (!pool) {
1330 PJSUA_UNLOCK();
1331 return PJ_ENOMEM;
1332 }
1333
1334 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001335 file_names, file_count,
1336 ptime, options, 0, &port);
1337 if (status != PJ_SUCCESS) {
1338 PJSUA_UNLOCK();
1339 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001340 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001341 return status;
1342 }
1343
Benny Prijonod5696da2007-07-17 16:25:45 +00001344 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001345 port, &port->info.name, &slot);
1346 if (status != PJ_SUCCESS) {
1347 pjmedia_port_destroy(port);
1348 PJSUA_UNLOCK();
1349 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001350 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001351 return status;
1352 }
1353
1354 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00001355 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001356 pjsua_var.player[file_id].port = port;
1357 pjsua_var.player[file_id].slot = slot;
1358
1359 if (p_id) *p_id = file_id;
1360
1361 ++pjsua_var.player_cnt;
1362
1363 PJSUA_UNLOCK();
1364 return PJ_SUCCESS;
1365
1366}
1367
1368
1369/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001370 * Get conference port ID associated with player.
1371 */
1372PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
1373{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001374 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001375 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1376
1377 return pjsua_var.player[id].slot;
1378}
1379
Benny Prijono469b1522006-12-26 03:05:17 +00001380/*
1381 * Get the media port for the player.
1382 */
1383PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_recorder_id id,
1384 pjmedia_port **p_port)
1385{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001386 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001387 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1388 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1389
1390 *p_port = pjsua_var.player[id].port;
1391
1392 return PJ_SUCCESS;
1393}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001394
1395/*
1396 * Set playback position.
1397 */
1398PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
1399 pj_uint32_t samples)
1400{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001401 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001402 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001403 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001404
1405 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
1406}
1407
1408
1409/*
1410 * Close the file, remove the player from the bridge, and free
1411 * resources associated with the file player.
1412 */
1413PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
1414{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001415 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001416 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1417
1418 PJSUA_LOCK();
1419
1420 if (pjsua_var.player[id].port) {
1421 pjmedia_conf_remove_port(pjsua_var.mconf,
1422 pjsua_var.player[id].slot);
1423 pjmedia_port_destroy(pjsua_var.player[id].port);
1424 pjsua_var.player[id].port = NULL;
1425 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00001426 pj_pool_release(pjsua_var.player[id].pool);
1427 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001428 pjsua_var.player_cnt--;
1429 }
1430
1431 PJSUA_UNLOCK();
1432
1433 return PJ_SUCCESS;
1434}
1435
1436
1437/*****************************************************************************
1438 * File recorder.
1439 */
1440
1441/*
1442 * Create a file recorder, and automatically connect this recorder to
1443 * the conference bridge.
1444 */
1445PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00001446 unsigned enc_type,
1447 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001448 pj_ssize_t max_size,
1449 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001450 pjsua_recorder_id *p_id)
1451{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001452 enum Format
1453 {
1454 FMT_UNKNOWN,
1455 FMT_WAV,
1456 FMT_MP3,
1457 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001458 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001459 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001460 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00001461 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00001462 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001463 pjmedia_port *port;
1464 pj_status_t status;
1465
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001466 /* Filename must present */
1467 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
1468
Benny Prijono00cae612006-07-31 15:19:36 +00001469 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001470 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001471
Benny Prijono8f310522006-10-20 11:08:49 +00001472 /* Don't support encoding type at present */
1473 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00001474
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001475 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
1476 return PJ_ETOOMANY;
1477
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001478 /* Determine the file format */
1479 ext.ptr = filename->ptr + filename->slen - 4;
1480 ext.slen = 4;
1481
1482 if (pj_stricmp2(&ext, ".wav") == 0)
1483 file_format = FMT_WAV;
1484 else if (pj_stricmp2(&ext, ".mp3") == 0)
1485 file_format = FMT_MP3;
1486 else {
1487 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
1488 "determine file format for %.*s",
1489 (int)filename->slen, filename->ptr));
1490 return PJ_ENOTSUP;
1491 }
1492
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001493 PJSUA_LOCK();
1494
1495 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
1496 if (pjsua_var.recorder[file_id].port == NULL)
1497 break;
1498 }
1499
1500 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
1501 /* This is unexpected */
1502 PJSUA_UNLOCK();
1503 pj_assert(0);
1504 return PJ_EBUG;
1505 }
1506
1507 pj_memcpy(path, filename->ptr, filename->slen);
1508 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001509
Benny Prijonod5696da2007-07-17 16:25:45 +00001510 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1511 if (!pool) {
1512 PJSUA_UNLOCK();
1513 return PJ_ENOMEM;
1514 }
1515
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001516 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00001517 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001518 pjsua_var.media_cfg.clock_rate,
1519 pjsua_var.mconf_cfg.channel_count,
1520 pjsua_var.mconf_cfg.samples_per_frame,
1521 pjsua_var.mconf_cfg.bits_per_sample,
1522 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001523 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00001524 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001525 port = NULL;
1526 status = PJ_ENOTSUP;
1527 }
1528
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001529 if (status != PJ_SUCCESS) {
1530 PJSUA_UNLOCK();
1531 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001532 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001533 return status;
1534 }
1535
Benny Prijonod5696da2007-07-17 16:25:45 +00001536 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001537 port, filename, &slot);
1538 if (status != PJ_SUCCESS) {
1539 pjmedia_port_destroy(port);
1540 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00001541 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001542 return status;
1543 }
1544
1545 pjsua_var.recorder[file_id].port = port;
1546 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00001547 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001548
1549 if (p_id) *p_id = file_id;
1550
1551 ++pjsua_var.rec_cnt;
1552
1553 PJSUA_UNLOCK();
1554 return PJ_SUCCESS;
1555}
1556
1557
1558/*
1559 * Get conference port associated with recorder.
1560 */
1561PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
1562{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001563 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1564 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001565 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1566
1567 return pjsua_var.recorder[id].slot;
1568}
1569
Benny Prijono469b1522006-12-26 03:05:17 +00001570/*
1571 * Get the media port for the recorder.
1572 */
1573PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
1574 pjmedia_port **p_port)
1575{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001576 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1577 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001578 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1579 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1580
1581 *p_port = pjsua_var.recorder[id].port;
1582 return PJ_SUCCESS;
1583}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001584
1585/*
1586 * Destroy recorder (this will complete recording).
1587 */
1588PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
1589{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001590 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1591 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001592 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1593
1594 PJSUA_LOCK();
1595
1596 if (pjsua_var.recorder[id].port) {
1597 pjmedia_conf_remove_port(pjsua_var.mconf,
1598 pjsua_var.recorder[id].slot);
1599 pjmedia_port_destroy(pjsua_var.recorder[id].port);
1600 pjsua_var.recorder[id].port = NULL;
1601 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00001602 pj_pool_release(pjsua_var.recorder[id].pool);
1603 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001604 pjsua_var.rec_cnt--;
1605 }
1606
1607 PJSUA_UNLOCK();
1608
1609 return PJ_SUCCESS;
1610}
1611
1612
1613/*****************************************************************************
1614 * Sound devices.
1615 */
1616
1617/*
1618 * Enum sound devices.
1619 */
1620PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
1621 unsigned *count)
1622{
1623 unsigned i, dev_count;
1624
1625 dev_count = pjmedia_snd_get_dev_count();
1626
1627 if (dev_count > *count) dev_count = *count;
1628
1629 for (i=0; i<dev_count; ++i) {
1630 const pjmedia_snd_dev_info *ci;
1631
1632 ci = pjmedia_snd_get_dev_info(i);
1633 pj_memcpy(&info[i], ci, sizeof(*ci));
1634 }
1635
1636 *count = dev_count;
1637
1638 return PJ_SUCCESS;
1639}
1640
1641
1642/* Close existing sound device */
1643static void close_snd_dev(void)
1644{
1645 /* Close sound device */
1646 if (pjsua_var.snd_port) {
1647 const pjmedia_snd_dev_info *cap_info, *play_info;
1648
1649 cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
1650 play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
1651
1652 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
1653 "%s sound capture device",
1654 play_info->name, cap_info->name));
1655
1656 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
1657 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1658 pjsua_var.snd_port = NULL;
1659 }
1660
1661 /* Close null sound device */
1662 if (pjsua_var.null_snd) {
1663 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
1664 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
1665 pjsua_var.null_snd = NULL;
1666 }
1667}
1668
1669/*
1670 * Select or change sound device. Application may call this function at
1671 * any time to replace current sound device.
1672 */
1673PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
1674 int playback_dev)
1675{
1676 pjmedia_port *conf_port;
Benny Prijono6dd967c2006-12-26 02:27:14 +00001677 const pjmedia_snd_dev_info *play_info;
Benny Prijono26056d82006-10-11 16:03:41 +00001678 unsigned clock_rates[] = { 0, 22050, 44100, 48000, 11025, 32000, 8000};
Benny Prijono658a1c52006-10-11 21:56:16 +00001679 unsigned selected_clock_rate = 0;
Benny Prijono26056d82006-10-11 16:03:41 +00001680 unsigned i;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00001681 pjmedia_snd_stream *strm;
1682 pjmedia_snd_stream_info si;
1683 pj_str_t tmp;
Benny Prijono26056d82006-10-11 16:03:41 +00001684 pj_status_t status = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001685
1686 /* Close existing sound port */
1687 close_snd_dev();
1688
1689
Benny Prijono26056d82006-10-11 16:03:41 +00001690 /* Set default clock rate */
1691 clock_rates[0] = pjsua_var.media_cfg.clock_rate;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001692
Benny Prijono26056d82006-10-11 16:03:41 +00001693 /* Attempts to open the sound device with different clock rates */
1694 for (i=0; i<PJ_ARRAY_SIZE(clock_rates); ++i) {
1695 char errmsg[PJ_ERR_MSG_SIZE];
Benny Prijonocf0b4b22007-10-06 17:31:09 +00001696 unsigned fps;
Benny Prijono26056d82006-10-11 16:03:41 +00001697
1698 PJ_LOG(4,(THIS_FILE,
1699 "pjsua_set_snd_dev(): attempting to open devices "
1700 "@%d Hz", clock_rates[i]));
1701
1702 /* Create the sound device. Sound port will start immediately. */
Benny Prijonocf0b4b22007-10-06 17:31:09 +00001703 fps = 1000 / pjsua_var.media_cfg.audio_frame_ptime;
Benny Prijono26056d82006-10-11 16:03:41 +00001704 status = pjmedia_snd_port_create(pjsua_var.pool, capture_dev,
1705 playback_dev,
1706 clock_rates[i], 1,
Benny Prijonocf0b4b22007-10-06 17:31:09 +00001707 clock_rates[i]/fps,
Benny Prijono26056d82006-10-11 16:03:41 +00001708 16, 0, &pjsua_var.snd_port);
1709
Benny Prijono658a1c52006-10-11 21:56:16 +00001710 if (status == PJ_SUCCESS) {
1711 selected_clock_rate = clock_rates[i];
Benny Prijono26056d82006-10-11 16:03:41 +00001712 break;
Benny Prijono658a1c52006-10-11 21:56:16 +00001713 }
Benny Prijono26056d82006-10-11 16:03:41 +00001714
1715 pj_strerror(status, errmsg, sizeof(errmsg));
Benny Prijono658a1c52006-10-11 21:56:16 +00001716 PJ_LOG(4, (THIS_FILE, "..failed: %s", errmsg));
Benny Prijono26056d82006-10-11 16:03:41 +00001717 }
1718
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001719 if (status != PJ_SUCCESS) {
1720 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
1721 return status;
1722 }
1723
1724 /* Get the port0 of the conference bridge. */
1725 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1726 pj_assert(conf_port != NULL);
1727
Benny Prijonof20687a2006-08-04 18:27:19 +00001728 /* Set AEC */
Benny Prijono5da50432006-08-07 10:24:52 +00001729 pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
1730 pjsua_var.media_cfg.ec_tail_len,
1731 pjsua_var.media_cfg.ec_options);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001732
Benny Prijono658a1c52006-10-11 21:56:16 +00001733 /* If there's mismatch between sound port and conference's port,
1734 * create a resample port to bridge them.
1735 */
1736 if (selected_clock_rate != pjsua_var.media_cfg.clock_rate) {
1737 pjmedia_port *resample_port;
1738
1739 status = pjmedia_resample_port_create(pjsua_var.pool, conf_port,
1740 selected_clock_rate, 0,
1741 &resample_port);
1742 if (status != PJ_SUCCESS) {
1743 pjsua_perror("Error creating resample port", THIS_FILE, status);
1744 return status;
1745 }
1746
1747 conf_port = resample_port;
1748 }
1749
Benny Prijono52a93912006-08-04 20:54:37 +00001750 /* Connect sound port to the bridge */
1751 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
1752 conf_port );
1753 if (status != PJ_SUCCESS) {
1754 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
1755 "sound device", status);
1756 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1757 pjsua_var.snd_port = NULL;
1758 return status;
1759 }
1760
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001761 /* Save the device IDs */
1762 pjsua_var.cap_dev = capture_dev;
1763 pjsua_var.play_dev = playback_dev;
1764
Benny Prijonoc53c6d72006-11-27 09:54:03 +00001765 /* Update sound device name. */
1766 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
1767 pjmedia_snd_stream_get_info(strm, &si);
1768 play_info = pjmedia_snd_get_dev_info(si.rec_id);
1769
1770 pjmedia_conf_set_port0_name(pjsua_var.mconf,
1771 pj_cstr(&tmp, play_info->name));
1772
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001773 return PJ_SUCCESS;
1774}
1775
1776
1777/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00001778 * Get currently active sound devices. If sound devices has not been created
1779 * (for example when pjsua_start() is not called), it is possible that
1780 * the function returns PJ_SUCCESS with -1 as device IDs.
1781 */
1782PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
1783 int *playback_dev)
1784{
1785 if (capture_dev) {
1786 *capture_dev = pjsua_var.cap_dev;
1787 }
1788 if (playback_dev) {
1789 *playback_dev = pjsua_var.play_dev;
1790 }
1791
1792 return PJ_SUCCESS;
1793}
1794
1795
1796/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001797 * Use null sound device.
1798 */
1799PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
1800{
1801 pjmedia_port *conf_port;
1802 pj_status_t status;
1803
1804 /* Close existing sound device */
1805 close_snd_dev();
1806
1807 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
1808
1809 /* Get the port0 of the conference bridge. */
1810 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1811 pj_assert(conf_port != NULL);
1812
1813 /* Create master port, connecting port0 of the conference bridge to
1814 * a null port.
1815 */
1816 status = pjmedia_master_port_create(pjsua_var.pool, pjsua_var.null_port,
1817 conf_port, 0, &pjsua_var.null_snd);
1818 if (status != PJ_SUCCESS) {
1819 pjsua_perror(THIS_FILE, "Unable to create null sound device",
1820 status);
1821 return status;
1822 }
1823
1824 /* Start the master port */
1825 status = pjmedia_master_port_start(pjsua_var.null_snd);
1826 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
1827
1828 return PJ_SUCCESS;
1829}
1830
1831
Benny Prijonoe909eac2006-07-27 22:04:56 +00001832
1833/*
1834 * Use no device!
1835 */
1836PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
1837{
1838 /* Close existing sound device */
1839 close_snd_dev();
1840
1841 pjsua_var.no_snd = PJ_TRUE;
1842 return pjmedia_conf_get_master_port(pjsua_var.mconf);
1843}
1844
1845
Benny Prijonof20687a2006-08-04 18:27:19 +00001846/*
1847 * Configure the AEC settings of the sound port.
1848 */
Benny Prijono5da50432006-08-07 10:24:52 +00001849PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00001850{
1851 pjsua_var.media_cfg.ec_tail_len = tail_ms;
1852
1853 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00001854 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
1855 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00001856
1857 return PJ_SUCCESS;
1858}
1859
1860
1861/*
1862 * Get current AEC tail length.
1863 */
Benny Prijono22dfe592006-08-06 12:07:13 +00001864PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00001865{
1866 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
1867 return PJ_SUCCESS;
1868}
1869
Benny Prijonoe909eac2006-07-27 22:04:56 +00001870
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001871/*****************************************************************************
1872 * Codecs.
1873 */
1874
1875/*
1876 * Enum all supported codecs in the system.
1877 */
1878PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
1879 unsigned *p_count )
1880{
1881 pjmedia_codec_mgr *codec_mgr;
1882 pjmedia_codec_info info[32];
1883 unsigned i, count, prio[32];
1884 pj_status_t status;
1885
1886 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1887 count = PJ_ARRAY_SIZE(info);
1888 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
1889 if (status != PJ_SUCCESS) {
1890 *p_count = 0;
1891 return status;
1892 }
1893
1894 if (count > *p_count) count = *p_count;
1895
1896 for (i=0; i<count; ++i) {
1897 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
1898 id[i].codec_id = pj_str(id[i].buf_);
1899 id[i].priority = (pj_uint8_t) prio[i];
1900 }
1901
1902 *p_count = count;
1903
1904 return PJ_SUCCESS;
1905}
1906
1907
1908/*
1909 * Change codec priority.
1910 */
1911PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
1912 pj_uint8_t priority )
1913{
1914 pjmedia_codec_mgr *codec_mgr;
1915
1916 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1917
1918 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
1919 priority);
1920}
1921
1922
1923/*
1924 * Get codec parameters.
1925 */
1926PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
1927 pjmedia_codec_param *param )
1928{
1929 const pjmedia_codec_info *info;
1930 pjmedia_codec_mgr *codec_mgr;
1931 unsigned count = 1;
1932 pj_status_t status;
1933
1934 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
1935
1936 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
1937 &count, &info, NULL);
1938 if (status != PJ_SUCCESS)
1939 return status;
1940
1941 if (count != 1)
1942 return PJ_ENOTFOUND;
1943
1944 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
1945 return status;
1946}
1947
1948
1949/*
1950 * Set codec parameters.
1951 */
1952PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
1953 const pjmedia_codec_param *param)
1954{
Benny Prijono00cae612006-07-31 15:19:36 +00001955 PJ_UNUSED_ARG(id);
1956 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001957 PJ_TODO(set_codec_param);
1958 return PJ_SUCCESS;
1959}