blob: edf13ba361623b00e73e735d9a1c554918deb56f [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
Benny Prijono844653c2008-12-23 17:27:53 +00003 * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
Benny Prijono32177c02008-06-20 22:44:47 +00004 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
Benny Prijonoeebe9af2006-06-13 22:57:13 +00005 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20#include <pjsua-lib/pjsua.h>
21#include <pjsua-lib/pjsua_internal.h>
22
23
24#define THIS_FILE "pjsua_media.c"
25
Nanang Izzuddin68559c32008-06-13 17:01:46 +000026#define DEFAULT_RTP_PORT 4000
27
28#define NULL_SND_DEV_ID -99
Benny Prijono80eee892007-11-03 22:43:23 +000029
30#ifndef PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
31# define PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT 0
32#endif
33
Benny Prijonoeebe9af2006-06-13 22:57:13 +000034
Benny Prijonode479562007-03-15 10:23:55 +000035/* Next RTP port to be used */
36static pj_uint16_t next_rtp_port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000037
Benny Prijonof798e502009-03-09 13:08:16 +000038/* Open sound dev */
39static pj_status_t open_snd_dev(pjmedia_aud_param *param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000040/* Close existing sound device */
41static void close_snd_dev(void);
Benny Prijonof798e502009-03-09 13:08:16 +000042/* Create audio device param */
43static pj_status_t create_aud_param(pjmedia_aud_param *param,
44 pjmedia_aud_dev_index capture_dev,
45 pjmedia_aud_dev_index playback_dev,
46 unsigned clock_rate,
47 unsigned channel_count,
48 unsigned samples_per_frame,
49 unsigned bits_per_sample);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000050
51
Benny Prijonof76e1392008-06-06 14:51:48 +000052static void pjsua_media_config_dup(pj_pool_t *pool,
53 pjsua_media_config *dst,
54 const pjsua_media_config *src)
55{
56 pj_memcpy(dst, src, sizeof(*src));
57 pj_strdup(pool, &dst->turn_server, &src->turn_server);
58 pj_stun_auth_cred_dup(pool, &dst->turn_auth_cred, &src->turn_auth_cred);
59}
60
Benny Prijonoeebe9af2006-06-13 22:57:13 +000061/**
62 * Init media subsystems.
63 */
64pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
65{
Benny Prijonoba5926a2007-05-02 11:29:37 +000066 pj_str_t codec_id = {NULL, 0};
Benny Prijono0498d902006-06-19 14:49:14 +000067 unsigned opt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000068 pj_status_t status;
69
Benny Prijonofc24e692007-01-27 18:31:51 +000070 /* To suppress warning about unused var when all codecs are disabled */
71 PJ_UNUSED_ARG(codec_id);
72
Benny Prijonof798e502009-03-09 13:08:16 +000073 /* Specify which audio device settings are save-able */
74 pjsua_var.aud_svmask = 0xFFFFFFFF;
75 /* These are not-settable */
76 pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
77 PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER |
78 PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER);
Benny Prijonoe506c8c2009-03-10 13:28:43 +000079 /* EC settings use different API */
80 pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EC |
81 PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijonof798e502009-03-09 13:08:16 +000082
Benny Prijonoeebe9af2006-06-13 22:57:13 +000083 /* Copy configuration */
Benny Prijonof76e1392008-06-06 14:51:48 +000084 pjsua_media_config_dup(pjsua_var.pool, &pjsua_var.media_cfg, cfg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000085
86 /* Normalize configuration */
Benny Prijono50f19b32008-03-11 13:15:43 +000087 if (pjsua_var.media_cfg.snd_clock_rate == 0) {
88 pjsua_var.media_cfg.snd_clock_rate = pjsua_var.media_cfg.clock_rate;
89 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +000090
91 if (pjsua_var.media_cfg.has_ioqueue &&
92 pjsua_var.media_cfg.thread_cnt == 0)
93 {
94 pjsua_var.media_cfg.thread_cnt = 1;
95 }
96
97 if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
98 pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
99 }
100
101 /* Create media endpoint. */
102 status = pjmedia_endpt_create(&pjsua_var.cp.factory,
103 pjsua_var.media_cfg.has_ioqueue? NULL :
104 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
105 pjsua_var.media_cfg.thread_cnt,
106 &pjsua_var.med_endpt);
107 if (status != PJ_SUCCESS) {
108 pjsua_perror(THIS_FILE,
109 "Media stack initialization has returned error",
110 status);
111 return status;
112 }
113
114 /* Register all codecs */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000115
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000116#if PJMEDIA_HAS_SPEEX_CODEC
117 /* Register speex. */
Nanang Izzuddin9dbad152008-06-10 18:56:10 +0000118 status = pjmedia_codec_speex_init(pjsua_var.med_endpt,
119 0,
120 pjsua_var.media_cfg.quality,
121 -1);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000122 if (status != PJ_SUCCESS) {
123 pjsua_perror(THIS_FILE, "Error initializing Speex codec",
124 status);
125 return status;
126 }
Benny Prijono7ca96da2006-08-07 12:11:40 +0000127
128 /* Set speex/16000 to higher priority*/
129 codec_id = pj_str("speex/16000");
130 pjmedia_codec_mgr_set_codec_priority(
131 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
132 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
133
134 /* Set speex/8000 to next higher priority*/
135 codec_id = pj_str("speex/8000");
136 pjmedia_codec_mgr_set_codec_priority(
137 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
138 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
139
140
141
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000142#endif /* PJMEDIA_HAS_SPEEX_CODEC */
143
Benny Prijono00cae612006-07-31 15:19:36 +0000144#if PJMEDIA_HAS_ILBC_CODEC
145 /* Register iLBC. */
146 status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt,
147 pjsua_var.media_cfg.ilbc_mode);
148 if (status != PJ_SUCCESS) {
149 pjsua_perror(THIS_FILE, "Error initializing iLBC codec",
150 status);
151 return status;
152 }
153#endif /* PJMEDIA_HAS_ILBC_CODEC */
154
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000155#if PJMEDIA_HAS_GSM_CODEC
156 /* Register GSM */
157 status = pjmedia_codec_gsm_init(pjsua_var.med_endpt);
158 if (status != PJ_SUCCESS) {
159 pjsua_perror(THIS_FILE, "Error initializing GSM codec",
160 status);
161 return status;
162 }
163#endif /* PJMEDIA_HAS_GSM_CODEC */
164
165#if PJMEDIA_HAS_G711_CODEC
166 /* Register PCMA and PCMU */
167 status = pjmedia_codec_g711_init(pjsua_var.med_endpt);
168 if (status != PJ_SUCCESS) {
169 pjsua_perror(THIS_FILE, "Error initializing G711 codec",
170 status);
171 return status;
172 }
173#endif /* PJMEDIA_HAS_G711_CODEC */
174
Benny Prijono7ffd7752008-03-17 14:07:53 +0000175#if PJMEDIA_HAS_G722_CODEC
176 status = pjmedia_codec_g722_init( pjsua_var.med_endpt );
177 if (status != PJ_SUCCESS) {
178 pjsua_perror(THIS_FILE, "Error initializing G722 codec",
179 status);
180 return status;
181 }
182#endif /* PJMEDIA_HAS_G722_CODEC */
183
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000184#if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000185 /* Register IPP codecs */
186 status = pjmedia_codec_ipp_init(pjsua_var.med_endpt);
187 if (status != PJ_SUCCESS) {
188 pjsua_perror(THIS_FILE, "Error initializing IPP codecs",
189 status);
190 return status;
191 }
192
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000193#endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000194
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000195#if PJMEDIA_HAS_PASSTHROUGH_CODECS
196 /* Register passthrough codecs */
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000197 {
198 unsigned aud_idx;
199 unsigned ext_fmt_cnt = 0;
200 pjmedia_format ext_fmts[32];
201 pjmedia_codec_passthrough_setting setting;
202
203 /* List extended formats supported by audio devices */
204 for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) {
205 pjmedia_aud_dev_info aud_info;
206 unsigned i;
207
208 status = pjmedia_aud_dev_get_info(aud_idx, &aud_info);
209 if (status != PJ_SUCCESS) {
210 pjsua_perror(THIS_FILE, "Error querying audio device info",
211 status);
212 return status;
213 }
214
215 /* Collect extended formats supported by this audio device */
216 for (i = 0; i < aud_info.ext_fmt_cnt; ++i) {
217 unsigned j;
218 pj_bool_t is_listed = PJ_FALSE;
219
220 /* See if this extended format is already in the list */
221 for (j = 0; j < ext_fmt_cnt && !is_listed; ++j) {
222 if (ext_fmts[j].id == aud_info.ext_fmt[i].id &&
223 ext_fmts[j].bitrate == aud_info.ext_fmt[i].bitrate)
224 {
225 is_listed = PJ_TRUE;
226 }
227 }
228
229 /* Put this format into the list, if it is not in the list */
230 if (!is_listed)
231 ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i];
232
233 pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts));
234 }
235 }
236
237 /* Init the passthrough codec with supported formats only */
238 setting.fmt_cnt = ext_fmt_cnt;
239 setting.fmts = ext_fmts;
240 status = pjmedia_codec_passthrough_init2(pjsua_var.med_endpt, &setting);
241 if (status != PJ_SUCCESS) {
242 pjsua_perror(THIS_FILE, "Error initializing passthrough codecs",
243 status);
244 return status;
245 }
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000246 }
247#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
248
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000249#if PJMEDIA_HAS_G7221_CODEC
250 /* Register G722.1 codecs */
251 status = pjmedia_codec_g7221_init(pjsua_var.med_endpt);
252 if (status != PJ_SUCCESS) {
253 pjsua_perror(THIS_FILE, "Error initializing G722.1 codec",
254 status);
255 return status;
256 }
257#endif /* PJMEDIA_HAS_G7221_CODEC */
258
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000259#if PJMEDIA_HAS_L16_CODEC
260 /* Register L16 family codecs, but disable all */
261 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
262 if (status != PJ_SUCCESS) {
263 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
264 status);
265 return status;
266 }
267
268 /* Disable ALL L16 codecs */
269 codec_id = pj_str("L16");
270 pjmedia_codec_mgr_set_codec_priority(
271 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
272 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
273
274#endif /* PJMEDIA_HAS_L16_CODEC */
275
276
277 /* Save additional conference bridge parameters for future
278 * reference.
279 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000280 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000281 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000282 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
283 pjsua_var.mconf_cfg.channel_count *
284 pjsua_var.media_cfg.audio_frame_ptime /
285 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000286
Benny Prijono0498d902006-06-19 14:49:14 +0000287 /* Init options for conference bridge. */
288 opt = PJMEDIA_CONF_NO_DEVICE;
289 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000290 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000291 {
292 opt |= PJMEDIA_CONF_SMALL_FILTER;
293 }
294 else if (pjsua_var.media_cfg.quality < 3) {
295 opt |= PJMEDIA_CONF_USE_LINEAR;
296 }
297
298
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000299 /* Init conference bridge. */
300 status = pjmedia_conf_create(pjsua_var.pool,
301 pjsua_var.media_cfg.max_media_ports,
302 pjsua_var.media_cfg.clock_rate,
303 pjsua_var.mconf_cfg.channel_count,
304 pjsua_var.mconf_cfg.samples_per_frame,
305 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000306 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000307 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000308 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000309 status);
310 return status;
311 }
312
Benny Prijonof798e502009-03-09 13:08:16 +0000313 /* Are we using the audio switchboard (a.k.a APS-Direct)? */
314 pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
315 ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
316
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000317 /* Create null port just in case user wants to use null sound. */
318 status = pjmedia_null_port_create(pjsua_var.pool,
319 pjsua_var.media_cfg.clock_rate,
320 pjsua_var.mconf_cfg.channel_count,
321 pjsua_var.mconf_cfg.samples_per_frame,
322 pjsua_var.mconf_cfg.bits_per_sample,
323 &pjsua_var.null_port);
324 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
325
Nanang Izzuddin69b69ae2009-04-14 15:18:30 +0000326#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
327 /* Initialize SRTP library. */
328 status = pjmedia_srtp_init_lib();
329 if (status != PJ_SUCCESS) {
330 pjsua_perror(THIS_FILE, "Error initializing SRTP library",
331 status);
332 return status;
333 }
334#endif
335
Benny Prijono6ba8c542007-10-16 01:34:14 +0000336 /* Perform NAT detection */
337 pjsua_detect_nat_type();
338
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000339 return PJ_SUCCESS;
340}
341
342
343/*
344 * Create RTP and RTCP socket pair, and possibly resolve their public
345 * address via STUN.
346 */
347static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
348 pjmedia_sock_info *skinfo)
349{
350 enum {
351 RTP_RETRY = 100
352 };
353 int i;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000354 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000355 pj_sockaddr_in mapped_addr[2];
356 pj_status_t status = PJ_SUCCESS;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000357 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000358 pj_sock_t sock[2];
359
Benny Prijonoc97608e2007-03-23 16:34:20 +0000360 /* Make sure STUN server resolution has completed */
361 status = pjsua_resolve_stun_server(PJ_TRUE);
362 if (status != PJ_SUCCESS) {
363 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
364 return status;
365 }
366
Benny Prijonode479562007-03-15 10:23:55 +0000367 if (next_rtp_port == 0)
368 next_rtp_port = (pj_uint16_t)cfg->port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000369
370 for (i=0; i<2; ++i)
371 sock[i] = PJ_INVALID_SOCKET;
372
Benny Prijono0a5cad82006-09-26 13:21:02 +0000373 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
374 if (cfg->bound_addr.slen) {
375 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
376 if (status != PJ_SUCCESS) {
377 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
378 status);
379 return status;
380 }
381 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000382
383 /* Loop retry to bind RTP and RTCP sockets. */
Benny Prijonode479562007-03-15 10:23:55 +0000384 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000385
386 /* Create and bind RTP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000387 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000388 if (status != PJ_SUCCESS) {
389 pjsua_perror(THIS_FILE, "socket() error", status);
390 return status;
391 }
392
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000393 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
394 next_rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000395 if (status != PJ_SUCCESS) {
396 pj_sock_close(sock[0]);
397 sock[0] = PJ_INVALID_SOCKET;
398 continue;
399 }
400
401 /* Create and bind RTCP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000402 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000403 if (status != PJ_SUCCESS) {
404 pjsua_perror(THIS_FILE, "socket() error", status);
405 pj_sock_close(sock[0]);
406 return status;
407 }
408
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000409 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
410 (pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000411 if (status != PJ_SUCCESS) {
412 pj_sock_close(sock[0]);
413 sock[0] = PJ_INVALID_SOCKET;
414
415 pj_sock_close(sock[1]);
416 sock[1] = PJ_INVALID_SOCKET;
417 continue;
418 }
419
420 /*
421 * If we're configured to use STUN, then find out the mapped address,
422 * and make sure that the mapped RTCP port is adjacent with the RTP.
423 */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000424 if (pjsua_var.stun_srv.addr.sa_family != 0) {
425 char ip_addr[32];
426 pj_str_t stun_srv;
427
428 pj_ansi_strcpy(ip_addr,
429 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
430 stun_srv = pj_str(ip_addr);
431
Benny Prijono14c2b862007-02-21 00:40:05 +0000432 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000433 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
434 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000435 mapped_addr);
436 if (status != PJ_SUCCESS) {
437 pjsua_perror(THIS_FILE, "STUN resolve error", status);
438 goto on_error;
439 }
440
Benny Prijono80eee892007-11-03 22:43:23 +0000441#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000442 if (pj_ntohs(mapped_addr[1].sin_port) ==
443 pj_ntohs(mapped_addr[0].sin_port)+1)
444 {
445 /* Success! */
446 break;
447 }
448
449 pj_sock_close(sock[0]);
450 sock[0] = PJ_INVALID_SOCKET;
451
452 pj_sock_close(sock[1]);
453 sock[1] = PJ_INVALID_SOCKET;
Benny Prijono80eee892007-11-03 22:43:23 +0000454#else
455 if (pj_ntohs(mapped_addr[1].sin_port) !=
456 pj_ntohs(mapped_addr[0].sin_port)+1)
457 {
458 PJ_LOG(4,(THIS_FILE,
459 "Note: STUN mapped RTCP port %d is not adjacent"
460 " to RTP port %d",
461 pj_ntohs(mapped_addr[1].sin_port),
462 pj_ntohs(mapped_addr[0].sin_port)));
463 }
464 /* Success! */
465 break;
466#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000467
Benny Prijono0a5cad82006-09-26 13:21:02 +0000468 } else if (cfg->public_addr.slen) {
469
470 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000471 (pj_uint16_t)next_rtp_port);
Benny Prijono0a5cad82006-09-26 13:21:02 +0000472 if (status != PJ_SUCCESS)
473 goto on_error;
474
475 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000476 (pj_uint16_t)(next_rtp_port+1));
Benny Prijono0a5cad82006-09-26 13:21:02 +0000477 if (status != PJ_SUCCESS)
478 goto on_error;
479
480 break;
481
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000482 } else {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000483
Benny Prijono42d08d22007-12-20 11:23:07 +0000484 if (bound_addr.sin_addr.s_addr == 0) {
485 pj_sockaddr addr;
486
487 /* Get local IP address. */
488 status = pj_gethostip(pj_AF_INET(), &addr);
489 if (status != PJ_SUCCESS)
490 goto on_error;
491
492 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
493 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000494
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000495 for (i=0; i<2; ++i) {
496 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
Benny Prijono42d08d22007-12-20 11:23:07 +0000497 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000498 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000499
Benny Prijonode479562007-03-15 10:23:55 +0000500 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
501 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000502 break;
503 }
504 }
505
506 if (sock[0] == PJ_INVALID_SOCKET) {
507 PJ_LOG(1,(THIS_FILE,
508 "Unable to find appropriate RTP/RTCP ports combination"));
509 goto on_error;
510 }
511
512
513 skinfo->rtp_sock = sock[0];
514 pj_memcpy(&skinfo->rtp_addr_name,
515 &mapped_addr[0], sizeof(pj_sockaddr_in));
516
517 skinfo->rtcp_sock = sock[1];
518 pj_memcpy(&skinfo->rtcp_addr_name,
519 &mapped_addr[1], sizeof(pj_sockaddr_in));
520
Benny Prijono8b22ce12008-02-08 12:57:55 +0000521 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000522 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
523 sizeof(addr_buf), 3)));
Benny Prijono8b22ce12008-02-08 12:57:55 +0000524 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000525 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
526 sizeof(addr_buf), 3)));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000527
Benny Prijonode479562007-03-15 10:23:55 +0000528 next_rtp_port += 2;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000529 return PJ_SUCCESS;
530
531on_error:
532 for (i=0; i<2; ++i) {
533 if (sock[i] != PJ_INVALID_SOCKET)
534 pj_sock_close(sock[i]);
535 }
536 return status;
537}
538
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000539/* Check if sound device is idle. */
540static void check_snd_dev_idle()
541{
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000542 unsigned call_cnt;
543
544 /* Get the call count, we shouldn't close the sound device when there is
545 * any calls active.
546 */
547 call_cnt = pjsua_call_get_count();
548
549 /* When this function is called from pjsua_media_channel_deinit() upon
550 * disconnecting call, actually the call count hasn't been updated/
551 * decreased. So we put additional check here, if there is only one
552 * call and it's in DISCONNECTED state, there is actually no active
553 * call.
554 */
555 if (call_cnt == 1) {
556 pjsua_call_id call_id;
557 pj_status_t status;
558
559 status = pjsua_enum_calls(&call_id, &call_cnt);
560 if (status == PJ_SUCCESS && call_cnt > 0 &&
561 !pjsua_call_is_active(call_id))
562 {
563 call_cnt = 0;
564 }
565 }
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000566
567 /* Activate sound device auto-close timer if sound device is idle.
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000568 * It is idle when there is no port connection in the bridge and
569 * there is no active call.
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000570 */
571 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) &&
572 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
573 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000574 call_cnt == 0 &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000575 pjsua_var.media_cfg.snd_auto_close_time >= 0)
576 {
577 pj_time_val delay;
578
579 delay.msec = 0;
580 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
581
582 pjsua_var.snd_idle_timer.id = PJ_TRUE;
583 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
584 &delay);
585 }
586}
587
588
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000589/* Timer callback to close sound device */
590static void close_snd_timer_cb( pj_timer_heap_t *th,
591 pj_timer_entry *entry)
592{
593 PJ_UNUSED_ARG(th);
594
Benny Prijono0f711b42009-05-06 19:08:43 +0000595 PJSUA_LOCK();
596 if (entry->id) {
597 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
598 pjsua_var.media_cfg.snd_auto_close_time));
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000599
Benny Prijono0f711b42009-05-06 19:08:43 +0000600 entry->id = PJ_FALSE;
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000601
Benny Prijono0f711b42009-05-06 19:08:43 +0000602 close_snd_dev();
603 }
604 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000605}
606
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000607
608/*
609 * Start pjsua media subsystem.
610 */
611pj_status_t pjsua_media_subsys_start(void)
612{
613 pj_status_t status;
614
615 /* Create media for calls, if none is specified */
616 if (pjsua_var.calls[0].med_tp == NULL) {
617 pjsua_transport_config transport_cfg;
618
619 /* Create default transport config */
620 pjsua_transport_config_default(&transport_cfg);
621 transport_cfg.port = DEFAULT_RTP_PORT;
622
623 status = pjsua_media_transports_create(&transport_cfg);
624 if (status != PJ_SUCCESS)
625 return status;
626 }
627
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000628 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
629 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000630
631 return PJ_SUCCESS;
632}
633
634
635/*
636 * Destroy pjsua media subsystem.
637 */
638pj_status_t pjsua_media_subsys_destroy(void)
639{
640 unsigned i;
641
642 close_snd_dev();
643
644 if (pjsua_var.mconf) {
645 pjmedia_conf_destroy(pjsua_var.mconf);
646 pjsua_var.mconf = NULL;
647 }
648
649 if (pjsua_var.null_port) {
650 pjmedia_port_destroy(pjsua_var.null_port);
651 pjsua_var.null_port = NULL;
652 }
653
654 /* Destroy file players */
655 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
656 if (pjsua_var.player[i].port) {
657 pjmedia_port_destroy(pjsua_var.player[i].port);
658 pjsua_var.player[i].port = NULL;
659 }
660 }
661
662 /* Destroy file recorders */
663 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
664 if (pjsua_var.recorder[i].port) {
665 pjmedia_port_destroy(pjsua_var.recorder[i].port);
666 pjsua_var.recorder[i].port = NULL;
667 }
668 }
669
670 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000671 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono311b63f2008-07-14 11:31:40 +0000672 if (pjsua_var.calls[i].med_tp_st != PJSUA_MED_TP_IDLE) {
673 pjsua_media_channel_deinit(i);
674 }
Benny Prijono40860c32008-09-04 13:55:33 +0000675 if (pjsua_var.calls[i].med_tp && pjsua_var.calls[i].med_tp_auto_del) {
676 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000677 }
Benny Prijono40860c32008-09-04 13:55:33 +0000678 pjsua_var.calls[i].med_tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000679 }
680
681 /* Destroy media endpoint. */
682 if (pjsua_var.med_endpt) {
683
684 /* Shutdown all codecs: */
685# if PJMEDIA_HAS_SPEEX_CODEC
686 pjmedia_codec_speex_deinit();
687# endif /* PJMEDIA_HAS_SPEEX_CODEC */
688
689# if PJMEDIA_HAS_GSM_CODEC
690 pjmedia_codec_gsm_deinit();
691# endif /* PJMEDIA_HAS_GSM_CODEC */
692
693# if PJMEDIA_HAS_G711_CODEC
694 pjmedia_codec_g711_deinit();
695# endif /* PJMEDIA_HAS_G711_CODEC */
696
Benny Prijono7ffd7752008-03-17 14:07:53 +0000697# if PJMEDIA_HAS_G722_CODEC
698 pjmedia_codec_g722_deinit();
699# endif /* PJMEDIA_HAS_G722_CODEC */
700
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000701# if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000702 pjmedia_codec_ipp_deinit();
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000703# endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000704
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000705# if PJMEDIA_HAS_PASSTHROUGH_CODECS
706 pjmedia_codec_passthrough_deinit();
707# endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
708
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000709# if PJMEDIA_HAS_G7221_CODEC
710 pjmedia_codec_g7221_deinit();
711# endif /* PJMEDIA_HAS_G7221_CODEC */
712
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000713# if PJMEDIA_HAS_L16_CODEC
714 pjmedia_codec_l16_deinit();
715# endif /* PJMEDIA_HAS_L16_CODEC */
716
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000717 pjmedia_endpt_destroy(pjsua_var.med_endpt);
718 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000719
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000720 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000721 // Not necessary, as pjmedia_snd_deinit() should have been called
722 // in pjmedia_endpt_destroy().
723 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000724 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000725
Benny Prijonode479562007-03-15 10:23:55 +0000726 /* Reset RTP port */
727 next_rtp_port = 0;
728
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000729 return PJ_SUCCESS;
730}
731
732
Benny Prijonoc97608e2007-03-23 16:34:20 +0000733/* Create normal UDP media transports */
734static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000735{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000736 unsigned i;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000737 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000738 pj_status_t status;
739
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000740 /* Create each media transport */
741 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
742
Benny Prijono617c5bc2007-04-02 19:51:21 +0000743 status = create_rtp_rtcp_sock(cfg, &skinfo);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000744 if (status != PJ_SUCCESS) {
745 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
746 status);
747 goto on_error;
748 }
Benny Prijonod8179652008-01-23 20:39:07 +0000749
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000750 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000751 &skinfo, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000752 &pjsua_var.calls[i].med_tp);
753 if (status != PJ_SUCCESS) {
754 pjsua_perror(THIS_FILE, "Unable to create media transport",
755 status);
756 goto on_error;
757 }
Benny Prijono00cae612006-07-31 15:19:36 +0000758
Benny Prijonod8179652008-01-23 20:39:07 +0000759 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
760 PJMEDIA_DIR_ENCODING,
761 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000762
Benny Prijonod8179652008-01-23 20:39:07 +0000763 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
764 PJMEDIA_DIR_DECODING,
765 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000766
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000767 }
768
Benny Prijonoc97608e2007-03-23 16:34:20 +0000769 return PJ_SUCCESS;
770
771on_error:
772 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
773 if (pjsua_var.calls[i].med_tp != NULL) {
774 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
775 pjsua_var.calls[i].med_tp = NULL;
776 }
777 }
778
779 return status;
780}
781
782
Benny Prijono096c56c2007-09-15 08:30:16 +0000783/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000784static void on_ice_complete(pjmedia_transport *tp,
785 pj_ice_strans_op op,
786 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000787{
Benny Prijonof76e1392008-06-06 14:51:48 +0000788 unsigned id;
Benny Prijono096c56c2007-09-15 08:30:16 +0000789 pj_bool_t found = PJ_FALSE;
790
Benny Prijono096c56c2007-09-15 08:30:16 +0000791 /* Find call which has this media transport */
792
793 PJSUA_LOCK();
794
Benny Prijonof76e1392008-06-06 14:51:48 +0000795 for (id=0; id<pjsua_var.ua_cfg.max_calls; ++id) {
796 if (pjsua_var.calls[id].med_tp == tp ||
797 pjsua_var.calls[id].med_orig == tp)
798 {
799 found = PJ_TRUE;
800 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000801 }
802 }
803
804 PJSUA_UNLOCK();
805
Benny Prijonof76e1392008-06-06 14:51:48 +0000806 if (!found)
807 return;
808
809 switch (op) {
810 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono224b4e22008-06-19 14:10:28 +0000811 pjsua_var.calls[id].med_tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000812 break;
813 case PJ_ICE_STRANS_OP_NEGOTIATION:
814 if (result != PJ_SUCCESS) {
815 pjsua_var.calls[id].media_st = PJSUA_CALL_MEDIA_ERROR;
816 pjsua_var.calls[id].media_dir = PJMEDIA_DIR_NONE;
817
818 if (pjsua_var.ua_cfg.cb.on_call_media_state) {
819 pjsua_var.ua_cfg.cb.on_call_media_state(id);
820 }
821 }
822 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000823 }
824}
825
826
Benny Prijonof76e1392008-06-06 14:51:48 +0000827/* Parse "HOST:PORT" format */
828static pj_status_t parse_host_port(const pj_str_t *host_port,
829 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000830{
Benny Prijonof76e1392008-06-06 14:51:48 +0000831 pj_str_t str_port;
832
833 str_port.ptr = pj_strchr(host_port, ':');
834 if (str_port.ptr != NULL) {
835 int iport;
836
837 host->ptr = host_port->ptr;
838 host->slen = (str_port.ptr - host->ptr);
839 str_port.ptr++;
840 str_port.slen = host_port->slen - host->slen - 1;
841 iport = (int)pj_strtoul(&str_port);
842 if (iport < 1 || iport > 65535)
843 return PJ_EINVAL;
844 *port = (pj_uint16_t)iport;
845 } else {
846 *host = *host_port;
847 *port = 0;
848 }
849
850 return PJ_SUCCESS;
851}
852
853/* Create ICE media transports (when ice is enabled) */
854static pj_status_t create_ice_media_transports(void)
855{
856 char stunip[PJ_INET6_ADDRSTRLEN];
857 pj_ice_strans_cfg ice_cfg;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000858 unsigned i;
859 pj_status_t status;
860
Benny Prijonoda9785b2007-04-02 20:43:06 +0000861 /* Make sure STUN server resolution has completed */
862 status = pjsua_resolve_stun_server(PJ_TRUE);
863 if (status != PJ_SUCCESS) {
864 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
865 return status;
866 }
867
Benny Prijonof76e1392008-06-06 14:51:48 +0000868 /* Create ICE stream transport configuration */
869 pj_ice_strans_cfg_default(&ice_cfg);
870 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
871 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
872 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
873
874 ice_cfg.af = pj_AF_INET();
875 ice_cfg.resolver = pjsua_var.resolver;
876
Benny Prijono329d6382009-05-29 13:04:03 +0000877 ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
878
Benny Prijonof76e1392008-06-06 14:51:48 +0000879 /* Configure STUN settings */
880 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
881 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
882 ice_cfg.stun.server = pj_str(stunip);
883 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
884 }
Benny Prijono329d6382009-05-29 13:04:03 +0000885 if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
886 ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
Benny Prijonof76e1392008-06-06 14:51:48 +0000887
888 /* Configure TURN settings */
889 if (pjsua_var.media_cfg.enable_turn) {
890 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
891 &ice_cfg.turn.server,
892 &ice_cfg.turn.port);
893 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
894 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
895 return PJ_EINVAL;
896 }
897 if (ice_cfg.turn.port == 0)
898 ice_cfg.turn.port = 3479;
899 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
900 pj_memcpy(&ice_cfg.turn.auth_cred,
901 &pjsua_var.media_cfg.turn_auth_cred,
902 sizeof(ice_cfg.turn.auth_cred));
903 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000904
Benny Prijonoc97608e2007-03-23 16:34:20 +0000905 /* Create each media transport */
906 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono096c56c2007-09-15 08:30:16 +0000907 pjmedia_ice_cb ice_cb;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000908 char name[32];
Benny Prijonof76e1392008-06-06 14:51:48 +0000909 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000910
Benny Prijono096c56c2007-09-15 08:30:16 +0000911 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
912 ice_cb.on_ice_complete = &on_ice_complete;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000913 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i);
Benny Prijono224b4e22008-06-19 14:10:28 +0000914 pjsua_var.calls[i].med_tp_ready = PJ_EPENDING;
Benny Prijonof76e1392008-06-06 14:51:48 +0000915
916 comp_cnt = 1;
Benny Prijono551af422008-08-07 09:55:52 +0000917 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
Benny Prijonof76e1392008-06-06 14:51:48 +0000918 ++comp_cnt;
919
920 status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
921 &ice_cfg, &ice_cb,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000922 &pjsua_var.calls[i].med_tp);
923 if (status != PJ_SUCCESS) {
924 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
925 status);
926 goto on_error;
927 }
928
Benny Prijonof76e1392008-06-06 14:51:48 +0000929 /* Wait until transport is initialized, or time out */
930 PJSUA_UNLOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000931 while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000932 pjsua_handle_events(100);
933 }
934 PJSUA_LOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000935 if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000936 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
Benny Prijono224b4e22008-06-19 14:10:28 +0000937 pjsua_var.calls[i].med_tp_ready);
938 status = pjsua_var.calls[i].med_tp_ready;
Benny Prijonof76e1392008-06-06 14:51:48 +0000939 goto on_error;
940 }
941
Benny Prijonod8179652008-01-23 20:39:07 +0000942 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
943 PJMEDIA_DIR_ENCODING,
944 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono11da9bc2007-09-15 08:55:00 +0000945
Benny Prijonod8179652008-01-23 20:39:07 +0000946 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
947 PJMEDIA_DIR_DECODING,
948 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000949 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000950
951 return PJ_SUCCESS;
952
953on_error:
954 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
955 if (pjsua_var.calls[i].med_tp != NULL) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000956 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000957 pjsua_var.calls[i].med_tp = NULL;
958 }
959 }
960
Benny Prijonoc97608e2007-03-23 16:34:20 +0000961 return status;
962}
963
964
965/*
966 * Create UDP media transports for all the calls. This function creates
967 * one UDP media transport for each call.
968 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +0000969PJ_DEF(pj_status_t) pjsua_media_transports_create(
970 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000971{
972 pjsua_transport_config cfg;
973 unsigned i;
974 pj_status_t status;
975
976
977 /* Make sure pjsua_init() has been called */
978 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
979
980 PJSUA_LOCK();
981
982 /* Delete existing media transports */
983 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono40860c32008-09-04 13:55:33 +0000984 if (pjsua_var.calls[i].med_tp != NULL &&
985 pjsua_var.calls[i].med_tp_auto_del)
986 {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000987 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
988 pjsua_var.calls[i].med_tp = NULL;
Nanang Izzuddind704a8b2008-09-23 16:34:07 +0000989 pjsua_var.calls[i].med_orig = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000990 }
991 }
992
993 /* Copy config */
994 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
995
Benny Prijono40860c32008-09-04 13:55:33 +0000996 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000997 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000998 status = create_ice_media_transports();
Benny Prijonoc97608e2007-03-23 16:34:20 +0000999 } else {
1000 status = create_udp_media_transports(&cfg);
1001 }
1002
Benny Prijono40860c32008-09-04 13:55:33 +00001003 /* Set media transport auto_delete to True */
1004 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
1005 pjsua_var.calls[i].med_tp_auto_del = PJ_TRUE;
1006 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001007
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001008 PJSUA_UNLOCK();
1009
1010 return status;
1011}
1012
Benny Prijono40860c32008-09-04 13:55:33 +00001013/*
1014 * Attach application's created media transports.
1015 */
1016PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
1017 unsigned count,
1018 pj_bool_t auto_delete)
1019{
1020 unsigned i;
1021
1022 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
1023
1024 /* Assign the media transports */
1025 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
1026 if (pjsua_var.calls[i].med_tp != NULL &&
1027 pjsua_var.calls[i].med_tp_auto_del)
1028 {
1029 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
1030 }
1031
1032 pjsua_var.calls[i].med_tp = tp[i].transport;
1033 pjsua_var.calls[i].med_tp_auto_del = auto_delete;
1034 }
1035
1036 return PJ_SUCCESS;
1037}
1038
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001039
Benny Prijonoc97608e2007-03-23 16:34:20 +00001040pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
Benny Prijonod8179652008-01-23 20:39:07 +00001041 pjsip_role_e role,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001042 int security_level,
Benny Prijono224b4e22008-06-19 14:10:28 +00001043 pj_pool_t *tmp_pool,
1044 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001045 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001046{
1047 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono224b4e22008-06-19 14:10:28 +00001048 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001049
Benny Prijonod8179652008-01-23 20:39:07 +00001050#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1051 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
1052 pjmedia_srtp_setting srtp_opt;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001053 pjmedia_transport *srtp = NULL;
Benny Prijonod8179652008-01-23 20:39:07 +00001054#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +00001055
Benny Prijonod8179652008-01-23 20:39:07 +00001056 PJ_UNUSED_ARG(role);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001057
Benny Prijonod8179652008-01-23 20:39:07 +00001058 /* Return error if media transport has not been created yet
1059 * (e.g. application is starting)
1060 */
1061 if (call->med_tp == NULL) {
Benny Prijono03789052008-09-16 14:30:50 +00001062 if (sip_err_code)
1063 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
Benny Prijonod8179652008-01-23 20:39:07 +00001064 return PJ_EBUSY;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001065 }
1066
Benny Prijonod8179652008-01-23 20:39:07 +00001067#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Benny Prijono53a7c702008-04-14 02:57:29 +00001068 /* This function may be called when SRTP transport already exists
1069 * (e.g: in re-invite, update), don't need to destroy/re-create.
1070 */
1071 if (!call->med_orig || call->med_tp == call->med_orig) {
1072
1073 /* Check if SRTP requires secure signaling */
1074 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
1075 if (security_level < acc->cfg.srtp_secure_signaling) {
1076 if (sip_err_code)
1077 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1078 return PJSIP_ESESSIONINSECURE;
1079 }
Benny Prijonod8179652008-01-23 20:39:07 +00001080 }
Benny Prijonod8179652008-01-23 20:39:07 +00001081
Benny Prijono53a7c702008-04-14 02:57:29 +00001082 /* Always create SRTP adapter */
1083 pjmedia_srtp_setting_default(&srtp_opt);
1084 srtp_opt.close_member_tp = PJ_FALSE;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001085 /* If media session has been ever established, let's use remote's
1086 * preference in SRTP usage policy, especially when it is stricter.
1087 */
1088 if (call->rem_srtp_use > acc->cfg.use_srtp)
1089 srtp_opt.use = call->rem_srtp_use;
1090 else
1091 srtp_opt.use = acc->cfg.use_srtp;
1092
Benny Prijono53a7c702008-04-14 02:57:29 +00001093 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
1094 call->med_tp,
1095 &srtp_opt, &srtp);
1096 if (status != PJ_SUCCESS) {
1097 if (sip_err_code)
1098 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
1099 return status;
1100 }
Benny Prijonod8179652008-01-23 20:39:07 +00001101
Benny Prijono53a7c702008-04-14 02:57:29 +00001102 /* Set SRTP as current media transport */
1103 call->med_orig = call->med_tp;
1104 call->med_tp = srtp;
1105 }
Benny Prijonod8179652008-01-23 20:39:07 +00001106#else
1107 call->med_orig = call->med_tp;
1108 PJ_UNUSED_ARG(security_level);
1109#endif
1110
Benny Prijonoa310bd22008-06-27 21:19:44 +00001111 /* Find out which media line in SDP that we support. If we are offerer,
1112 * audio will be at index 0 in SDP.
1113 */
1114 if (rem_sdp == 0) {
1115 call->audio_idx = 0;
1116 }
1117 /* Otherwise find out the candidate audio media line in SDP */
1118 else {
1119 unsigned i;
1120 pj_bool_t srtp_active;
1121
1122#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1123 srtp_active = acc->cfg.use_srtp && srtp != NULL;
1124#else
1125 srtp_active = PJ_FALSE;
1126#endif
1127
1128 /* Media count must have been checked */
1129 pj_assert(rem_sdp->media_count != 0);
1130
1131 for (i=0; i<rem_sdp->media_count; ++i) {
1132 const pjmedia_sdp_media *m = rem_sdp->media[i];
1133
1134 /* Skip if media is not audio */
1135 if (pj_stricmp2(&m->desc.media, "audio") != 0)
1136 continue;
1137
1138 /* Skip if media is disabled */
1139 if (m->desc.port == 0)
1140 continue;
1141
1142 /* Skip if transport is not supported */
1143 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") != 0 &&
1144 pj_stricmp2(&m->desc.transport, "RTP/SAVP") != 0)
1145 {
1146 continue;
1147 }
1148
1149 if (call->audio_idx == -1) {
1150 call->audio_idx = i;
1151 } else {
1152 /* We've found multiple candidates. This could happen
1153 * e.g. when remote is offering both RTP/AVP and RTP/AVP,
1154 * or when remote for some reason offers two audio.
1155 */
1156
1157 if (srtp_active &&
1158 pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0)
1159 {
1160 /* Prefer RTP/SAVP when our media transport is SRTP */
1161 call->audio_idx = i;
1162 } else if (!srtp_active &&
1163 pj_stricmp2(&m->desc.transport, "RTP/AVP")==0)
1164 {
1165 /* Prefer RTP/AVP when our media transport is NOT SRTP */
1166 call->audio_idx = i;
1167 }
1168 }
1169 }
1170 }
1171
1172 /* Reject offer if we couldn't find a good m=audio line in offer */
1173 if (call->audio_idx < 0) {
Benny Prijonoab8dba92008-06-27 21:59:15 +00001174 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001175 pjsua_media_channel_deinit(call_id);
Benny Prijonoab8dba92008-06-27 21:59:15 +00001176 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001177 }
1178
1179 PJ_LOG(4,(THIS_FILE, "Media index %d selected for call %d",
1180 call->audio_idx, call->index));
1181
Benny Prijono224b4e22008-06-19 14:10:28 +00001182 /* Create the media transport */
1183 status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001184 rem_sdp, call->audio_idx);
Benny Prijono224b4e22008-06-19 14:10:28 +00001185 if (status != PJ_SUCCESS) {
1186 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1187 pjsua_media_channel_deinit(call_id);
1188 return status;
1189 }
1190
1191 call->med_tp_st = PJSUA_MED_TP_INIT;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001192 return PJ_SUCCESS;
1193}
1194
1195pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1196 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001197 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001198 pjmedia_sdp_session **p_sdp,
1199 int *sip_status_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001200{
Benny Prijonoa310bd22008-06-27 21:19:44 +00001201 enum { MAX_MEDIA = 1 };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001202 pjmedia_sdp_session *sdp;
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001203 pjmedia_transport_info tpinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001204 pjsua_call *call = &pjsua_var.calls[call_id];
1205 pj_status_t status;
1206
Benny Prijono55e82352007-05-10 20:49:08 +00001207 /* Return error if media transport has not been created yet
1208 * (e.g. application is starting)
1209 */
1210 if (call->med_tp == NULL) {
1211 return PJ_EBUSY;
1212 }
1213
Benny Prijonoa310bd22008-06-27 21:19:44 +00001214 /* Media index must have been determined before */
1215 pj_assert(call->audio_idx != -1);
1216
Benny Prijono224b4e22008-06-19 14:10:28 +00001217 /* Create media if it's not created. This could happen when call is
1218 * currently on-hold
1219 */
1220 if (call->med_tp_st == PJSUA_MED_TP_IDLE) {
1221 pjsip_role_e role;
1222 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1223 status = pjsua_media_channel_init(call_id, role, call->secure_level,
1224 pool, rem_sdp, sip_status_code);
1225 if (status != PJ_SUCCESS)
1226 return status;
1227 }
1228
Benny Prijono617c5bc2007-04-02 19:51:21 +00001229 /* Get media socket info */
Benny Prijono734fc2d2008-03-17 16:05:35 +00001230 pjmedia_transport_info_init(&tpinfo);
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001231 pjmedia_transport_get_info(call->med_tp, &tpinfo);
Benny Prijono617c5bc2007-04-02 19:51:21 +00001232
1233 /* Create SDP */
Benny Prijonod8179652008-01-23 20:39:07 +00001234 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA,
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001235 &tpinfo.sock_info, &sdp);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001236 if (status != PJ_SUCCESS) {
1237 if (sip_status_code) *sip_status_code = 500;
Benny Prijono224b4e22008-06-19 14:10:28 +00001238 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001239 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001240
Benny Prijonoa310bd22008-06-27 21:19:44 +00001241 /* If we're answering and the selected media is not the first media
1242 * in SDP, then fill in the unselected media with with zero port.
1243 * Otherwise we'll crash in transport_encode_sdp() because the media
1244 * lines are not aligned between offer and answer.
1245 */
1246 if (rem_sdp && call->audio_idx != 0) {
1247 unsigned i;
1248
1249 for (i=0; i<rem_sdp->media_count; ++i) {
1250 const pjmedia_sdp_media *rem_m = rem_sdp->media[i];
1251 pjmedia_sdp_media *m;
1252 const pjmedia_sdp_attr *a;
1253
1254 if ((int)i == call->audio_idx)
1255 continue;
1256
1257 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1258 pj_strdup(pool, &m->desc.media, &rem_m->desc.media);
1259 pj_strdup(pool, &m->desc.transport, &rem_m->desc.transport);
1260 m->desc.port = 0;
1261
1262 /* Add one format, copy from the offer. And copy the corresponding
1263 * rtpmap and fmtp attributes too.
1264 */
1265 m->desc.fmt_count = 1;
1266 pj_strdup(pool, &m->desc.fmt[0], &rem_m->desc.fmt[0]);
1267 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1268 "rtpmap", &m->desc.fmt[0])) != NULL)
1269 {
1270 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1271 }
1272 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1273 "fmtp", &m->desc.fmt[0])) != NULL)
1274 {
1275 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1276 }
1277
1278 if (i==sdp->media_count)
1279 sdp->media[sdp->media_count++] = m;
1280 else {
1281 pj_array_insert(sdp->media, sizeof(sdp->media[0]),
1282 sdp->media_count, i, &m);
1283 ++sdp->media_count;
1284 }
1285 }
1286 }
1287
Benny Prijono6ba8c542007-10-16 01:34:14 +00001288 /* Add NAT info in the SDP */
1289 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1290 pjmedia_sdp_attr *a;
1291 pj_str_t value;
1292 char nat_info[80];
1293
1294 value.ptr = nat_info;
1295 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1296 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1297 "%d", pjsua_var.nat_type);
1298 } else {
1299 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1300 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1301 "%d %s",
1302 pjsua_var.nat_type,
1303 type_name);
1304 }
1305
1306 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1307
1308 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1309
1310 }
1311
Benny Prijonod8179652008-01-23 20:39:07 +00001312 /* Give the SDP to media transport */
Benny Prijono224b4e22008-06-19 14:10:28 +00001313 status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001314 call->audio_idx);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001315 if (status != PJ_SUCCESS) {
1316 if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono224b4e22008-06-19 14:10:28 +00001317 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001318 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001319
1320 *p_sdp = sdp;
1321 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001322}
1323
1324
1325static void stop_media_session(pjsua_call_id call_id)
1326{
1327 pjsua_call *call = &pjsua_var.calls[call_id];
1328
1329 if (call->conf_slot != PJSUA_INVALID_ID) {
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001330 if (pjsua_var.mconf) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001331 pjsua_conf_remove_port(call->conf_slot);
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001332 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001333 call->conf_slot = PJSUA_INVALID_ID;
1334 }
1335
1336 if (call->session) {
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001337 pjmedia_rtcp_stat stat;
1338
Nanang Izzuddin437d77c2008-08-26 18:04:15 +00001339 if (pjmedia_session_get_stream_stat(call->session, 0, &stat)
1340 == PJ_SUCCESS)
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001341 {
1342 /* Save RTP timestamp & sequence, so when media session is
1343 * restarted, those values will be restored as the initial
1344 * RTP timestamp & sequence of the new media session. So in
1345 * the same call session, RTP timestamp and sequence are
1346 * guaranteed to be contigue.
1347 */
1348 call->rtp_tx_seq_ts_set = 1 | (1 << 1);
1349 call->rtp_tx_seq = stat.rtp_tx_last_seq;
1350 call->rtp_tx_ts = stat.rtp_tx_last_ts;
1351 }
1352
Benny Prijonofc13bf62008-02-20 08:56:15 +00001353 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1354 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0);
1355 }
1356
Benny Prijonoc97608e2007-03-23 16:34:20 +00001357 pjmedia_session_destroy(call->session);
1358 call->session = NULL;
1359
1360 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
1361 call_id));
1362
1363 }
1364
1365 call->media_st = PJSUA_CALL_MEDIA_NONE;
1366}
1367
1368pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1369{
1370 pjsua_call *call = &pjsua_var.calls[call_id];
1371
1372 stop_media_session(call_id);
1373
Benny Prijono224b4e22008-06-19 14:10:28 +00001374 if (call->med_tp_st != PJSUA_MED_TP_IDLE) {
1375 pjmedia_transport_media_stop(call->med_tp);
1376 call->med_tp_st = PJSUA_MED_TP_IDLE;
1377 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001378
Benny Prijono311b63f2008-07-14 11:31:40 +00001379 if (call->med_orig && call->med_tp && call->med_tp != call->med_orig) {
Benny Prijonod8179652008-01-23 20:39:07 +00001380 pjmedia_transport_close(call->med_tp);
1381 call->med_tp = call->med_orig;
1382 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00001383
1384 check_snd_dev_idle();
1385
Benny Prijonoc97608e2007-03-23 16:34:20 +00001386 return PJ_SUCCESS;
1387}
1388
1389
1390/*
1391 * DTMF callback from the stream.
1392 */
1393static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1394 int digit)
1395{
1396 PJ_UNUSED_ARG(strm);
1397
Benny Prijono0c068262008-02-14 14:38:52 +00001398 /* For discussions about call mutex protection related to this
1399 * callback, please see ticket #460:
1400 * http://trac.pjsip.org/repos/ticket/460#comment:4
1401 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001402 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1403 pjsua_call_id call_id;
1404
Benny Prijonod8179652008-01-23 20:39:07 +00001405 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001406 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1407 }
1408}
1409
1410
1411pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijono224b4e22008-06-19 14:10:28 +00001412 const pjmedia_sdp_session *local_sdp,
Benny Prijonodbce2cf2007-03-28 16:24:00 +00001413 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001414{
1415 int prev_media_st = 0;
1416 pjsua_call *call = &pjsua_var.calls[call_id];
1417 pjmedia_session_info sess_info;
Benny Prijono91e567e2007-12-28 08:51:58 +00001418 pjmedia_stream_info *si = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001419 pjmedia_port *media_port;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001420 pj_status_t status;
1421
1422 /* Destroy existing media session, if any. */
1423 prev_media_st = call->media_st;
1424 stop_media_session(call->index);
1425
1426 /* Create media session info based on SDP parameters.
Benny Prijonoc97608e2007-03-23 16:34:20 +00001427 */
1428 status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
1429 pjsua_var.med_endpt,
Benny Prijono91e567e2007-12-28 08:51:58 +00001430 PJMEDIA_MAX_SDP_MEDIA, &sess_info,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001431 local_sdp, remote_sdp);
1432 if (status != PJ_SUCCESS)
1433 return status;
1434
Benny Prijonoa310bd22008-06-27 21:19:44 +00001435 /* Find which session is audio */
1436 PJ_ASSERT_RETURN(call->audio_idx != -1, PJ_EBUG);
1437 PJ_ASSERT_RETURN(call->audio_idx < (int)sess_info.stream_cnt, PJ_EBUG);
1438 si = &sess_info.stream_info[call->audio_idx];
Benny Prijono91e567e2007-12-28 08:51:58 +00001439
1440 /* Reset session info with only one media stream */
1441 sess_info.stream_cnt = 1;
1442 if (si != &sess_info.stream_info[0])
1443 pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001444
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001445 /* Check if no media is active */
Benny Prijono91e567e2007-12-28 08:51:58 +00001446 if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001447 {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001448 /* Call media state */
1449 call->media_st = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001450
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001451 /* Call media direction */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001452 call->media_dir = PJMEDIA_DIR_NONE;
1453
Benny Prijonod8179652008-01-23 20:39:07 +00001454 /* Shutdown transport's session */
1455 pjmedia_transport_media_stop(call->med_tp);
Benny Prijono224b4e22008-06-19 14:10:28 +00001456 call->med_tp_st = PJSUA_MED_TP_IDLE;
Benny Prijono667952e2007-04-02 19:27:54 +00001457
Benny Prijonoc97608e2007-03-23 16:34:20 +00001458 /* No need because we need keepalive? */
1459
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001460 /* Close upper entry of transport stack */
1461 if (call->med_orig && (call->med_tp != call->med_orig)) {
1462 pjmedia_transport_close(call->med_tp);
1463 call->med_tp = call->med_orig;
1464 }
1465
Benny Prijonoc97608e2007-03-23 16:34:20 +00001466 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001467 pjmedia_transport_info tp_info;
1468
Benny Prijono224b4e22008-06-19 14:10:28 +00001469 /* Start/restart media transport */
Benny Prijonod8179652008-01-23 20:39:07 +00001470 status = pjmedia_transport_media_start(call->med_tp,
1471 call->inv->pool,
1472 local_sdp, remote_sdp, 0);
1473 if (status != PJ_SUCCESS)
1474 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001475
Benny Prijono224b4e22008-06-19 14:10:28 +00001476 call->med_tp_st = PJSUA_MED_TP_RUNNING;
1477
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001478 /* Get remote SRTP usage policy */
1479 pjmedia_transport_info_init(&tp_info);
1480 pjmedia_transport_get_info(call->med_tp, &tp_info);
1481 if (tp_info.specific_info_cnt > 0) {
1482 int i;
1483 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1484 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1485 {
1486 pjmedia_srtp_info *srtp_info =
1487 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1488
1489 call->rem_srtp_use = srtp_info->peer_use;
1490 break;
1491 }
1492 }
1493 }
1494
Benny Prijonoc97608e2007-03-23 16:34:20 +00001495 /* Override ptime, if this option is specified. */
1496 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001497 si->param->setting.frm_per_pkt = (pj_uint8_t)
1498 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1499 if (si->param->setting.frm_per_pkt == 0)
1500 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001501 }
1502
1503 /* Disable VAD, if this option is specified. */
1504 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001505 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001506 }
1507
1508
1509 /* Optionally, application may modify other stream settings here
1510 * (such as jitter buffer parameters, codec ptime, etc.)
1511 */
Benny Prijono91e567e2007-12-28 08:51:58 +00001512 si->jb_init = pjsua_var.media_cfg.jb_init;
1513 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1514 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1515 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001516
Benny Prijono8147f402007-11-21 14:50:07 +00001517 /* Set SSRC */
Benny Prijono91e567e2007-12-28 08:51:58 +00001518 si->ssrc = call->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00001519
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001520 /* Set RTP timestamp & sequence, normally these value are intialized
1521 * automatically when stream session created, but for some cases (e.g:
1522 * call reinvite, call update) timestamp and sequence need to be kept
1523 * contigue.
1524 */
1525 si->rtp_ts = call->rtp_tx_ts;
1526 si->rtp_seq = call->rtp_tx_seq;
1527 si->rtp_seq_ts_set = call->rtp_tx_seq_ts_set;
1528
Benny Prijonoc97608e2007-03-23 16:34:20 +00001529 /* Create session based on session info. */
1530 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
1531 &call->med_tp,
1532 call, &call->session );
1533 if (status != PJ_SUCCESS) {
1534 return status;
1535 }
1536
1537 /* If DTMF callback is installed by application, install our
1538 * callback to the session.
1539 */
1540 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1541 pjmedia_session_set_dtmf_callback(call->session, 0,
1542 &dtmf_callback,
Benny Prijonod8179652008-01-23 20:39:07 +00001543 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001544 }
1545
1546 /* Get the port interface of the first stream in the session.
1547 * We need the port interface to add to the conference bridge.
1548 */
1549 pjmedia_session_get_port(call->session, 0, &media_port);
1550
Benny Prijonofc13bf62008-02-20 08:56:15 +00001551 /* Notify application about stream creation.
1552 * Note: application may modify media_port to point to different
1553 * media port
1554 */
1555 if (pjsua_var.ua_cfg.cb.on_stream_created) {
1556 pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session,
1557 0, &media_port);
1558 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001559
1560 /*
1561 * Add the call to conference bridge.
1562 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001563 {
1564 char tmp[PJSIP_MAX_URL_SIZE];
1565 pj_str_t port_name;
1566
1567 port_name.ptr = tmp;
1568 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1569 call->inv->dlg->remote.info->uri,
1570 tmp, sizeof(tmp));
1571 if (port_name.slen < 1) {
1572 port_name = pj_str("call");
1573 }
1574 status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
1575 media_port,
1576 &port_name,
1577 (unsigned*)&call->conf_slot);
1578 if (status != PJ_SUCCESS) {
1579 return status;
1580 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001581 }
1582
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001583 /* Call media direction */
Benny Prijono91e567e2007-12-28 08:51:58 +00001584 call->media_dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001585
1586 /* Call media state */
1587 if (call->local_hold)
1588 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
1589 else if (call->media_dir == PJMEDIA_DIR_DECODING)
1590 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
1591 else
1592 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001593 }
1594
1595 /* Print info. */
1596 {
1597 char info[80];
1598 int info_len = 0;
1599 unsigned i;
1600
1601 for (i=0; i<sess_info.stream_cnt; ++i) {
1602 int len;
1603 const char *dir;
1604 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1605
1606 switch (strm_info->dir) {
1607 case PJMEDIA_DIR_NONE:
1608 dir = "inactive";
1609 break;
1610 case PJMEDIA_DIR_ENCODING:
1611 dir = "sendonly";
1612 break;
1613 case PJMEDIA_DIR_DECODING:
1614 dir = "recvonly";
1615 break;
1616 case PJMEDIA_DIR_ENCODING_DECODING:
1617 dir = "sendrecv";
1618 break;
1619 default:
1620 dir = "unknown";
1621 break;
1622 }
1623 len = pj_ansi_sprintf( info+info_len,
1624 ", stream #%d: %.*s (%s)", i,
1625 (int)strm_info->fmt.encoding_name.slen,
1626 strm_info->fmt.encoding_name.ptr,
1627 dir);
1628 if (len > 0)
1629 info_len += len;
1630 }
1631 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1632 }
1633
1634 return PJ_SUCCESS;
1635}
1636
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001637/*
1638 * Get maxinum number of conference ports.
1639 */
1640PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1641{
1642 return pjsua_var.media_cfg.max_media_ports;
1643}
1644
1645
1646/*
1647 * Get current number of active ports in the bridge.
1648 */
1649PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1650{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001651 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001652 unsigned count = PJ_ARRAY_SIZE(ports);
1653 pj_status_t status;
1654
1655 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1656 if (status != PJ_SUCCESS)
1657 count = 0;
1658
1659 return count;
1660}
1661
1662
1663/*
1664 * Enumerate all conference ports.
1665 */
1666PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1667 unsigned *count)
1668{
1669 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1670}
1671
1672
1673/*
1674 * Get information about the specified conference port
1675 */
1676PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1677 pjsua_conf_port_info *info)
1678{
1679 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001680 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001681 pj_status_t status;
1682
1683 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1684 if (status != PJ_SUCCESS)
1685 return status;
1686
Benny Prijonoac623b32006-07-03 15:19:31 +00001687 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001688 info->slot_id = id;
1689 info->name = cinfo.name;
1690 info->clock_rate = cinfo.clock_rate;
1691 info->channel_count = cinfo.channel_count;
1692 info->samples_per_frame = cinfo.samples_per_frame;
1693 info->bits_per_sample = cinfo.bits_per_sample;
1694
1695 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001696 info->listener_cnt = cinfo.listener_cnt;
1697 for (i=0; i<cinfo.listener_cnt; ++i) {
1698 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001699 }
1700
1701 return PJ_SUCCESS;
1702}
1703
1704
1705/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001706 * Add arbitrary media port to PJSUA's conference bridge.
1707 */
1708PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1709 pjmedia_port *port,
1710 pjsua_conf_port_id *p_id)
1711{
1712 pj_status_t status;
1713
1714 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1715 port, NULL, (unsigned*)p_id);
1716 if (status != PJ_SUCCESS) {
1717 if (p_id)
1718 *p_id = PJSUA_INVALID_ID;
1719 }
1720
1721 return status;
1722}
1723
1724
1725/*
1726 * Remove arbitrary slot from the conference bridge.
1727 */
1728PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1729{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001730 pj_status_t status;
1731
1732 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1733 check_snd_dev_idle();
1734
1735 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00001736}
1737
1738
1739/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001740 * Establish unidirectional media flow from souce to sink.
1741 */
1742PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1743 pjsua_conf_port_id sink)
1744{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001745 /* If sound device idle timer is active, cancel it first. */
Benny Prijono0f711b42009-05-06 19:08:43 +00001746 PJSUA_LOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001747 if (pjsua_var.snd_idle_timer.id) {
1748 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
1749 pjsua_var.snd_idle_timer.id = PJ_FALSE;
1750 }
Benny Prijono0f711b42009-05-06 19:08:43 +00001751 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001752
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001753
Benny Prijonof798e502009-03-09 13:08:16 +00001754 /* For audio switchboard (i.e. APS-Direct):
1755 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001756 * (format, clock rate, channel count) must match to peer's.
1757 * Note that sound device can be reopened only if it doesn't have
1758 * any connection.
1759 */
Benny Prijonof798e502009-03-09 13:08:16 +00001760 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001761 pjmedia_conf_port_info port0_info;
1762 pjmedia_conf_port_info peer_info;
1763 unsigned peer_id;
1764 pj_bool_t need_reopen = PJ_FALSE;
1765 pj_status_t status;
1766
1767 peer_id = (source!=0)? source : sink;
1768 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
1769 &peer_info);
1770 pj_assert(status == PJ_SUCCESS);
1771
1772 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
1773 pj_assert(status == PJ_SUCCESS);
1774
1775 /* Check if sound device is instantiated. */
1776 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1777 !pjsua_var.no_snd);
1778
1779 /* Check if sound device need to reopen because it needs to modify
1780 * settings to match its peer. Sound device must be idle in this case
1781 * though.
1782 */
1783 if (!need_reopen &&
1784 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
1785 {
1786 need_reopen = (peer_info.format.id != port0_info.format.id ||
1787 peer_info.format.bitrate != port0_info.format.bitrate ||
1788 peer_info.clock_rate != port0_info.clock_rate ||
1789 peer_info.channel_count != port0_info.channel_count);
1790 }
1791
1792 if (need_reopen) {
Benny Prijonod65f78c2009-06-03 18:59:37 +00001793 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
1794 pjmedia_aud_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001795
Benny Prijonod65f78c2009-06-03 18:59:37 +00001796 /* Create parameter based on peer info */
1797 status = create_aud_param(&param, pjsua_var.cap_dev,
1798 pjsua_var.play_dev,
1799 peer_info.clock_rate,
1800 peer_info.channel_count,
1801 peer_info.samples_per_frame,
1802 peer_info.bits_per_sample);
1803 if (status != PJ_SUCCESS) {
1804 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1805 return status;
1806 }
Benny Prijonof798e502009-03-09 13:08:16 +00001807
Benny Prijonod65f78c2009-06-03 18:59:37 +00001808 /* And peer format */
1809 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
1810 param.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
1811 param.ext_fmt = peer_info.format;
1812 }
Benny Prijono10454dc2009-02-21 14:21:59 +00001813
Benny Prijonod65f78c2009-06-03 18:59:37 +00001814 status = open_snd_dev(&param);
1815 if (status != PJ_SUCCESS) {
1816 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1817 return status;
1818 }
1819 } else {
1820 /* Null-audio */
1821 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1822 if (status != PJ_SUCCESS) {
1823 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1824 return status;
1825 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001826 }
1827 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001828
Benny Prijonof798e502009-03-09 13:08:16 +00001829 } else {
1830 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001831
Benny Prijonof798e502009-03-09 13:08:16 +00001832 /* Create sound port if none is instantiated */
1833 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1834 !pjsua_var.no_snd)
1835 {
1836 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001837
Benny Prijonof798e502009-03-09 13:08:16 +00001838 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1839 if (status != PJ_SUCCESS) {
1840 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1841 return status;
1842 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001843 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001844
Benny Prijonof798e502009-03-09 13:08:16 +00001845 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001846
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001847 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
1848}
1849
1850
1851/*
1852 * Disconnect media flow from the source to destination port.
1853 */
1854PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
1855 pjsua_conf_port_id sink)
1856{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001857 pj_status_t status;
1858
1859 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001860 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001861
1862 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001863}
1864
1865
Benny Prijono6dd967c2006-12-26 02:27:14 +00001866/*
1867 * Adjust the signal level to be transmitted from the bridge to the
1868 * specified port by making it louder or quieter.
1869 */
1870PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
1871 float level)
1872{
1873 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
1874 (int)((level-1) * 128));
1875}
1876
1877/*
1878 * Adjust the signal level to be received from the specified port (to
1879 * the bridge) by making it louder or quieter.
1880 */
1881PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
1882 float level)
1883{
1884 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
1885 (int)((level-1) * 128));
1886}
1887
1888
1889/*
1890 * Get last signal level transmitted to or received from the specified port.
1891 */
1892PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
1893 unsigned *tx_level,
1894 unsigned *rx_level)
1895{
1896 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
1897 tx_level, rx_level);
1898}
1899
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001900/*****************************************************************************
1901 * File player.
1902 */
1903
Benny Prijonod5696da2007-07-17 16:25:45 +00001904static char* get_basename(const char *path, unsigned len)
1905{
1906 char *p = ((char*)path) + len;
1907
1908 if (len==0)
1909 return p;
1910
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001911 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00001912
1913 return (p==path) ? p : p+1;
1914}
1915
1916
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001917/*
1918 * Create a file player, and automatically connect this player to
1919 * the conference bridge.
1920 */
1921PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1922 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001923 pjsua_player_id *p_id)
1924{
1925 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001926 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00001927 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001928 pjmedia_port *port;
1929 pj_status_t status;
1930
1931 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1932 return PJ_ETOOMANY;
1933
1934 PJSUA_LOCK();
1935
1936 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1937 if (pjsua_var.player[file_id].port == NULL)
1938 break;
1939 }
1940
1941 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1942 /* This is unexpected */
1943 PJSUA_UNLOCK();
1944 pj_assert(0);
1945 return PJ_EBUG;
1946 }
1947
1948 pj_memcpy(path, filename->ptr, filename->slen);
1949 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00001950
1951 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1952 if (!pool) {
1953 PJSUA_UNLOCK();
1954 return PJ_ENOMEM;
1955 }
1956
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00001957 status = pjmedia_wav_player_port_create(
1958 pool, path,
1959 pjsua_var.mconf_cfg.samples_per_frame *
1960 1000 / pjsua_var.media_cfg.channel_count /
1961 pjsua_var.media_cfg.clock_rate,
1962 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001963 if (status != PJ_SUCCESS) {
1964 PJSUA_UNLOCK();
1965 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001966 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001967 return status;
1968 }
1969
Benny Prijono5297af92008-03-18 13:40:40 +00001970 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001971 port, filename, &slot);
1972 if (status != PJ_SUCCESS) {
1973 pjmedia_port_destroy(port);
1974 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00001975 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1976 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001977 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001978 return status;
1979 }
1980
Benny Prijonoa66c3312007-01-21 23:12:40 +00001981 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00001982 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001983 pjsua_var.player[file_id].port = port;
1984 pjsua_var.player[file_id].slot = slot;
1985
1986 if (p_id) *p_id = file_id;
1987
1988 ++pjsua_var.player_cnt;
1989
1990 PJSUA_UNLOCK();
1991 return PJ_SUCCESS;
1992}
1993
1994
1995/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00001996 * Create a file playlist media port, and automatically add the port
1997 * to the conference bridge.
1998 */
1999PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
2000 unsigned file_count,
2001 const pj_str_t *label,
2002 unsigned options,
2003 pjsua_player_id *p_id)
2004{
2005 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00002006 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002007 pjmedia_port *port;
2008 pj_status_t status;
2009
2010 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2011 return PJ_ETOOMANY;
2012
2013 PJSUA_LOCK();
2014
2015 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2016 if (pjsua_var.player[file_id].port == NULL)
2017 break;
2018 }
2019
2020 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2021 /* This is unexpected */
2022 PJSUA_UNLOCK();
2023 pj_assert(0);
2024 return PJ_EBUG;
2025 }
2026
2027
2028 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
2029 pjsua_var.media_cfg.clock_rate;
2030
Benny Prijonod5696da2007-07-17 16:25:45 +00002031 pool = pjsua_pool_create("playlist", 1000, 1000);
2032 if (!pool) {
2033 PJSUA_UNLOCK();
2034 return PJ_ENOMEM;
2035 }
2036
2037 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002038 file_names, file_count,
2039 ptime, options, 0, &port);
2040 if (status != PJ_SUCCESS) {
2041 PJSUA_UNLOCK();
2042 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002043 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002044 return status;
2045 }
2046
Benny Prijonod5696da2007-07-17 16:25:45 +00002047 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002048 port, &port->info.name, &slot);
2049 if (status != PJ_SUCCESS) {
2050 pjmedia_port_destroy(port);
2051 PJSUA_UNLOCK();
2052 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002053 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002054 return status;
2055 }
2056
2057 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00002058 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002059 pjsua_var.player[file_id].port = port;
2060 pjsua_var.player[file_id].slot = slot;
2061
2062 if (p_id) *p_id = file_id;
2063
2064 ++pjsua_var.player_cnt;
2065
2066 PJSUA_UNLOCK();
2067 return PJ_SUCCESS;
2068
2069}
2070
2071
2072/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002073 * Get conference port ID associated with player.
2074 */
2075PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
2076{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002077 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002078 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2079
2080 return pjsua_var.player[id].slot;
2081}
2082
Benny Prijono469b1522006-12-26 03:05:17 +00002083/*
2084 * Get the media port for the player.
2085 */
Benny Prijonobe41d862008-01-18 13:24:28 +00002086PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00002087 pjmedia_port **p_port)
2088{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002089 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002090 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2091 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2092
2093 *p_port = pjsua_var.player[id].port;
2094
2095 return PJ_SUCCESS;
2096}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002097
2098/*
2099 * Set playback position.
2100 */
2101PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
2102 pj_uint32_t samples)
2103{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002104 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002105 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002106 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002107
2108 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
2109}
2110
2111
2112/*
2113 * Close the file, remove the player from the bridge, and free
2114 * resources associated with the file player.
2115 */
2116PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
2117{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002118 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002119 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2120
2121 PJSUA_LOCK();
2122
2123 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002124 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002125 pjmedia_port_destroy(pjsua_var.player[id].port);
2126 pjsua_var.player[id].port = NULL;
2127 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002128 pj_pool_release(pjsua_var.player[id].pool);
2129 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002130 pjsua_var.player_cnt--;
2131 }
2132
2133 PJSUA_UNLOCK();
2134
2135 return PJ_SUCCESS;
2136}
2137
2138
2139/*****************************************************************************
2140 * File recorder.
2141 */
2142
2143/*
2144 * Create a file recorder, and automatically connect this recorder to
2145 * the conference bridge.
2146 */
2147PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00002148 unsigned enc_type,
2149 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002150 pj_ssize_t max_size,
2151 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002152 pjsua_recorder_id *p_id)
2153{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002154 enum Format
2155 {
2156 FMT_UNKNOWN,
2157 FMT_WAV,
2158 FMT_MP3,
2159 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002160 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002161 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002162 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00002163 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00002164 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002165 pjmedia_port *port;
2166 pj_status_t status;
2167
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002168 /* Filename must present */
2169 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
2170
Benny Prijono00cae612006-07-31 15:19:36 +00002171 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002172 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002173
Benny Prijono8f310522006-10-20 11:08:49 +00002174 /* Don't support encoding type at present */
2175 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002176
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002177 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
2178 return PJ_ETOOMANY;
2179
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002180 /* Determine the file format */
2181 ext.ptr = filename->ptr + filename->slen - 4;
2182 ext.slen = 4;
2183
2184 if (pj_stricmp2(&ext, ".wav") == 0)
2185 file_format = FMT_WAV;
2186 else if (pj_stricmp2(&ext, ".mp3") == 0)
2187 file_format = FMT_MP3;
2188 else {
2189 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
2190 "determine file format for %.*s",
2191 (int)filename->slen, filename->ptr));
2192 return PJ_ENOTSUP;
2193 }
2194
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002195 PJSUA_LOCK();
2196
2197 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
2198 if (pjsua_var.recorder[file_id].port == NULL)
2199 break;
2200 }
2201
2202 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
2203 /* This is unexpected */
2204 PJSUA_UNLOCK();
2205 pj_assert(0);
2206 return PJ_EBUG;
2207 }
2208
2209 pj_memcpy(path, filename->ptr, filename->slen);
2210 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002211
Benny Prijonod5696da2007-07-17 16:25:45 +00002212 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2213 if (!pool) {
2214 PJSUA_UNLOCK();
2215 return PJ_ENOMEM;
2216 }
2217
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002218 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00002219 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002220 pjsua_var.media_cfg.clock_rate,
2221 pjsua_var.mconf_cfg.channel_count,
2222 pjsua_var.mconf_cfg.samples_per_frame,
2223 pjsua_var.mconf_cfg.bits_per_sample,
2224 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002225 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00002226 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002227 port = NULL;
2228 status = PJ_ENOTSUP;
2229 }
2230
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002231 if (status != PJ_SUCCESS) {
2232 PJSUA_UNLOCK();
2233 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002234 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002235 return status;
2236 }
2237
Benny Prijonod5696da2007-07-17 16:25:45 +00002238 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002239 port, filename, &slot);
2240 if (status != PJ_SUCCESS) {
2241 pjmedia_port_destroy(port);
2242 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00002243 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002244 return status;
2245 }
2246
2247 pjsua_var.recorder[file_id].port = port;
2248 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00002249 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002250
2251 if (p_id) *p_id = file_id;
2252
2253 ++pjsua_var.rec_cnt;
2254
2255 PJSUA_UNLOCK();
2256 return PJ_SUCCESS;
2257}
2258
2259
2260/*
2261 * Get conference port associated with recorder.
2262 */
2263PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
2264{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002265 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2266 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002267 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2268
2269 return pjsua_var.recorder[id].slot;
2270}
2271
Benny Prijono469b1522006-12-26 03:05:17 +00002272/*
2273 * Get the media port for the recorder.
2274 */
2275PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
2276 pjmedia_port **p_port)
2277{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002278 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2279 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002280 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2281 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2282
2283 *p_port = pjsua_var.recorder[id].port;
2284 return PJ_SUCCESS;
2285}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002286
2287/*
2288 * Destroy recorder (this will complete recording).
2289 */
2290PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
2291{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002292 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2293 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002294 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2295
2296 PJSUA_LOCK();
2297
2298 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002299 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002300 pjmedia_port_destroy(pjsua_var.recorder[id].port);
2301 pjsua_var.recorder[id].port = NULL;
2302 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002303 pj_pool_release(pjsua_var.recorder[id].pool);
2304 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002305 pjsua_var.rec_cnt--;
2306 }
2307
2308 PJSUA_UNLOCK();
2309
2310 return PJ_SUCCESS;
2311}
2312
2313
2314/*****************************************************************************
2315 * Sound devices.
2316 */
2317
2318/*
2319 * Enum sound devices.
2320 */
Benny Prijonof798e502009-03-09 13:08:16 +00002321
2322PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002323 unsigned *count)
2324{
2325 unsigned i, dev_count;
2326
Benny Prijono10454dc2009-02-21 14:21:59 +00002327 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002328
2329 if (dev_count > *count) dev_count = *count;
2330
2331 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00002332 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002333
Benny Prijono10454dc2009-02-21 14:21:59 +00002334 status = pjmedia_aud_dev_get_info(i, &info[i]);
2335 if (status != PJ_SUCCESS)
2336 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002337 }
2338
2339 *count = dev_count;
2340
2341 return PJ_SUCCESS;
2342}
Benny Prijonof798e502009-03-09 13:08:16 +00002343
2344
Benny Prijono10454dc2009-02-21 14:21:59 +00002345PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
2346 unsigned *count)
2347{
2348 unsigned i, dev_count;
2349
2350 dev_count = pjmedia_aud_dev_count();
2351
2352 if (dev_count > *count) dev_count = *count;
2353 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
2354
2355 for (i=0; i<dev_count; ++i) {
2356 pjmedia_aud_dev_info ai;
2357 pj_status_t status;
2358
2359 status = pjmedia_aud_dev_get_info(i, &ai);
2360 if (status != PJ_SUCCESS)
2361 return status;
2362
2363 strncpy(info[i].name, ai.name, sizeof(info[i].name));
2364 info[i].name[sizeof(info[i].name)-1] = '\0';
2365 info[i].input_count = ai.input_count;
2366 info[i].output_count = ai.output_count;
2367 info[i].default_samples_per_sec = ai.default_samples_per_sec;
2368 }
2369
2370 *count = dev_count;
2371
2372 return PJ_SUCCESS;
2373}
Benny Prijono10454dc2009-02-21 14:21:59 +00002374
Benny Prijonof798e502009-03-09 13:08:16 +00002375/* Create audio device parameter to open the device */
2376static pj_status_t create_aud_param(pjmedia_aud_param *param,
2377 pjmedia_aud_dev_index capture_dev,
2378 pjmedia_aud_dev_index playback_dev,
2379 unsigned clock_rate,
2380 unsigned channel_count,
2381 unsigned samples_per_frame,
2382 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002383{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002384 pj_status_t status;
2385
Benny Prijono96e74f32009-02-22 12:00:12 +00002386 /* Normalize device ID with new convention about default device ID */
2387 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
2388 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
2389
Benny Prijono10454dc2009-02-21 14:21:59 +00002390 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00002391 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00002392 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00002393 pjsua_perror(THIS_FILE, "Error retrieving default audio "
2394 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00002395 return status;
2396 }
Benny Prijonof798e502009-03-09 13:08:16 +00002397 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
2398 param->rec_id = capture_dev;
2399 param->play_id = playback_dev;
2400 param->clock_rate = clock_rate;
2401 param->channel_count = channel_count;
2402 param->samples_per_frame = samples_per_frame;
2403 param->bits_per_sample = bits_per_sample;
2404
2405 /* Update the setting with user preference */
2406#define update_param(cap, field) \
2407 if (pjsua_var.aud_param.flags & cap) { \
2408 param->flags |= cap; \
2409 param->field = pjsua_var.aud_param.field; \
2410 }
2411 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
2412 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
2413 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
2414 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
2415#undef update_param
2416
Benny Prijono10454dc2009-02-21 14:21:59 +00002417 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00002418 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
2419 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
2420 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
2421 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
2422
Benny Prijono10454dc2009-02-21 14:21:59 +00002423 /* EC settings */
2424 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00002425 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
2426 param->ec_enabled = PJ_TRUE;
2427 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00002428 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00002429 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00002430 }
2431
Benny Prijonof798e502009-03-09 13:08:16 +00002432 return PJ_SUCCESS;
2433}
Benny Prijono26056d82006-10-11 16:03:41 +00002434
Benny Prijonof798e502009-03-09 13:08:16 +00002435/* Internal: the first time the audio device is opened (during app
2436 * startup), retrieve the audio settings such as volume level
2437 * so that aud_get_settings() will work.
2438 */
2439static pj_status_t update_initial_aud_param()
2440{
2441 pjmedia_aud_stream *strm;
2442 pjmedia_aud_param param;
2443 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00002444
Benny Prijonof798e502009-03-09 13:08:16 +00002445 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002446
Benny Prijonof798e502009-03-09 13:08:16 +00002447 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00002448
Benny Prijonof798e502009-03-09 13:08:16 +00002449 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002450 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00002451 pjsua_perror(THIS_FILE, "Error audio stream "
2452 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002453 return status;
2454 }
2455
Benny Prijonof798e502009-03-09 13:08:16 +00002456#define update_saved_param(cap, field) \
2457 if (param.flags & cap) { \
2458 pjsua_var.aud_param.flags |= cap; \
2459 pjsua_var.aud_param.field = param.field; \
2460 }
2461
2462 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
2463 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
2464 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
2465 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
2466#undef update_saved_param
2467
2468 return PJ_SUCCESS;
2469}
2470
2471/* Get format name */
2472static const char *get_fmt_name(pj_uint32_t id)
2473{
2474 static char name[8];
2475
2476 if (id == PJMEDIA_FORMAT_L16)
2477 return "PCM";
2478 pj_memcpy(name, &id, 4);
2479 name[4] = '\0';
2480 return name;
2481}
2482
2483/* Open sound device with the setting. */
2484static pj_status_t open_snd_dev(pjmedia_aud_param *param)
2485{
2486 pjmedia_port *conf_port;
2487 pj_status_t status;
2488
2489 PJ_ASSERT_RETURN(param, PJ_EINVAL);
2490
2491 /* Check if NULL sound device is used */
2492 if (NULL_SND_DEV_ID==param->rec_id || NULL_SND_DEV_ID==param->play_id) {
2493 return pjsua_set_null_snd_dev();
2494 }
2495
2496 /* Close existing sound port */
2497 close_snd_dev();
2498
2499 /* Create memory pool for sound device. */
2500 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2501 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2502
2503
2504 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
2505 get_fmt_name(param->ext_fmt.id),
2506 param->clock_rate, param->channel_count,
2507 param->samples_per_frame / param->channel_count * 1000 /
2508 param->clock_rate));
2509
2510 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
2511 param, &pjsua_var.snd_port);
2512 if (status != PJ_SUCCESS)
2513 return status;
2514
2515 /* Get the port0 of the conference bridge. */
2516 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2517 pj_assert(conf_port != NULL);
2518
2519 /* For conference bridge, resample if necessary if the bridge's
2520 * clock rate is different than the sound device's clock rate.
2521 */
2522 if (!pjsua_var.is_mswitch &&
2523 param->ext_fmt.id == PJMEDIA_FORMAT_PCM &&
2524 conf_port->info.clock_rate != param->clock_rate)
2525 {
2526 pjmedia_port *resample_port;
2527 unsigned resample_opt = 0;
2528
2529 if (pjsua_var.media_cfg.quality >= 3 &&
2530 pjsua_var.media_cfg.quality <= 4)
2531 {
2532 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
2533 }
2534 else if (pjsua_var.media_cfg.quality < 3) {
2535 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
2536 }
2537
2538 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
2539 conf_port,
2540 param->clock_rate,
2541 resample_opt,
2542 &resample_port);
2543 if (status != PJ_SUCCESS) {
2544 char errmsg[PJ_ERR_MSG_SIZE];
2545 pj_strerror(status, errmsg, sizeof(errmsg));
2546 PJ_LOG(4, (THIS_FILE,
2547 "Error creating resample port: %s",
2548 errmsg));
2549 close_snd_dev();
2550 return status;
2551 }
2552
2553 conf_port = resample_port;
2554 }
2555
2556 /* Otherwise for audio switchboard, the switch's port0 setting is
2557 * derived from the sound device setting, so update the setting.
2558 */
2559 if (pjsua_var.is_mswitch) {
2560 pj_memcpy(&conf_port->info.format, &param->ext_fmt,
2561 sizeof(conf_port->info.format));
2562 conf_port->info.clock_rate = param->clock_rate;
2563 conf_port->info.samples_per_frame = param->samples_per_frame;
2564 conf_port->info.channel_count = param->channel_count;
2565 conf_port->info.bits_per_sample = 16;
2566 }
2567
2568 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00002569 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
2570 conf_port );
2571 if (status != PJ_SUCCESS) {
2572 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
2573 "sound device", status);
2574 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2575 pjsua_var.snd_port = NULL;
2576 return status;
2577 }
2578
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002579 /* Save the device IDs */
Benny Prijonof798e502009-03-09 13:08:16 +00002580 pjsua_var.cap_dev = param->rec_id;
2581 pjsua_var.play_dev = param->play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002582
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002583 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00002584 {
2585 pjmedia_aud_dev_info rec_info;
2586 pjmedia_aud_stream *strm;
2587 pjmedia_aud_param si;
2588 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002589
Benny Prijonof798e502009-03-09 13:08:16 +00002590 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2591 status = pjmedia_aud_stream_get_param(strm, &si);
2592 if (status == PJ_SUCCESS)
2593 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00002594
Benny Prijonof798e502009-03-09 13:08:16 +00002595 if (status==PJ_SUCCESS) {
2596 if (param->clock_rate != pjsua_var.media_cfg.clock_rate) {
2597 char tmp_buf[128];
2598 int tmp_buf_len = sizeof(tmp_buf);
2599
2600 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
2601 "%s (%dKHz)",
2602 rec_info.name,
2603 param->clock_rate/1000);
2604 pj_strset(&tmp, tmp_buf, tmp_buf_len);
2605 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
2606 } else {
2607 pjmedia_conf_set_port0_name(pjsua_var.mconf,
2608 pj_cstr(&tmp, rec_info.name));
2609 }
2610 }
2611
2612 /* Any error is not major, let it through */
2613 status = PJ_SUCCESS;
2614 };
2615
2616 /* If this is the first time the audio device is open, retrieve some
2617 * settings from the device (such as volume settings) so that the
2618 * pjsua_snd_get_setting() work.
2619 */
2620 if (pjsua_var.aud_open_cnt == 0) {
2621 update_initial_aud_param();
2622 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00002623 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002624
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002625 return PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00002626}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002627
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002628
Benny Prijonof798e502009-03-09 13:08:16 +00002629/* Close existing sound device */
2630static void close_snd_dev(void)
2631{
2632 /* Close sound device */
2633 if (pjsua_var.snd_port) {
2634 pjmedia_aud_dev_info cap_info, play_info;
2635 pjmedia_aud_stream *strm;
2636 pjmedia_aud_param param;
2637
2638 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2639 pjmedia_aud_stream_get_param(strm, &param);
2640
2641 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
2642 cap_info.name[0] = '\0';
2643 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
2644 play_info.name[0] = '\0';
2645
2646 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
2647 "%s sound capture device",
2648 play_info.name, cap_info.name));
2649
2650 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
2651 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2652 pjsua_var.snd_port = NULL;
2653 }
2654
2655 /* Close null sound device */
2656 if (pjsua_var.null_snd) {
2657 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
2658 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
2659 pjsua_var.null_snd = NULL;
2660 }
2661
2662 if (pjsua_var.snd_pool)
2663 pj_pool_release(pjsua_var.snd_pool);
2664 pjsua_var.snd_pool = NULL;
2665}
2666
2667
2668/*
2669 * Select or change sound device. Application may call this function at
2670 * any time to replace current sound device.
2671 */
2672PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
2673 int playback_dev)
2674{
2675 unsigned alt_cr_cnt = 1;
2676 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
2677 unsigned i;
2678 pj_status_t status = -1;
2679
Benny Prijono23ea21a2009-06-03 12:43:06 +00002680 /* Null-sound */
2681 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
2682 return pjsua_set_null_snd_dev();
2683 }
2684
Benny Prijonof798e502009-03-09 13:08:16 +00002685 /* Set default clock rate */
2686 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
2687 if (alt_cr[0] == 0)
2688 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
2689
2690 /* Allow retrying of different clock rate if we're using conference
2691 * bridge (meaning audio format is always PCM), otherwise lock on
2692 * to one clock rate.
2693 */
2694 if (pjsua_var.is_mswitch) {
2695 alt_cr_cnt = 1;
2696 } else {
2697 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
2698 }
2699
2700 /* Attempts to open the sound device with different clock rates */
2701 for (i=0; i<alt_cr_cnt; ++i) {
2702 pjmedia_aud_param param;
2703 unsigned samples_per_frame;
2704
2705 /* Create the default audio param */
2706 samples_per_frame = alt_cr[i] *
2707 pjsua_var.media_cfg.audio_frame_ptime *
2708 pjsua_var.media_cfg.channel_count / 1000;
2709 status = create_aud_param(&param, capture_dev, playback_dev,
2710 alt_cr[i], pjsua_var.media_cfg.channel_count,
2711 samples_per_frame, 16);
2712 if (status != PJ_SUCCESS)
2713 return status;
2714
2715 /* Open! */
2716 status = open_snd_dev(&param);
2717 if (status == PJ_SUCCESS)
2718 break;
2719 }
2720
2721 if (status != PJ_SUCCESS) {
2722 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
2723 return status;
2724 }
2725
2726 return PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002727}
2728
2729
2730/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00002731 * Get currently active sound devices. If sound devices has not been created
2732 * (for example when pjsua_start() is not called), it is possible that
2733 * the function returns PJ_SUCCESS with -1 as device IDs.
2734 */
2735PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
2736 int *playback_dev)
2737{
2738 if (capture_dev) {
2739 *capture_dev = pjsua_var.cap_dev;
2740 }
2741 if (playback_dev) {
2742 *playback_dev = pjsua_var.play_dev;
2743 }
2744
2745 return PJ_SUCCESS;
2746}
2747
2748
2749/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002750 * Use null sound device.
2751 */
2752PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
2753{
2754 pjmedia_port *conf_port;
2755 pj_status_t status;
2756
2757 /* Close existing sound device */
2758 close_snd_dev();
2759
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002760 /* Create memory pool for sound device. */
2761 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2762 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2763
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002764 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
2765
2766 /* Get the port0 of the conference bridge. */
2767 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2768 pj_assert(conf_port != NULL);
2769
2770 /* Create master port, connecting port0 of the conference bridge to
2771 * a null port.
2772 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002773 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002774 conf_port, 0, &pjsua_var.null_snd);
2775 if (status != PJ_SUCCESS) {
2776 pjsua_perror(THIS_FILE, "Unable to create null sound device",
2777 status);
2778 return status;
2779 }
2780
2781 /* Start the master port */
2782 status = pjmedia_master_port_start(pjsua_var.null_snd);
2783 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2784
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002785 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2786 pjsua_var.play_dev = NULL_SND_DEV_ID;
2787
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002788 return PJ_SUCCESS;
2789}
2790
2791
Benny Prijonoe909eac2006-07-27 22:04:56 +00002792
2793/*
2794 * Use no device!
2795 */
2796PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2797{
2798 /* Close existing sound device */
2799 close_snd_dev();
2800
2801 pjsua_var.no_snd = PJ_TRUE;
2802 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2803}
2804
2805
Benny Prijonof20687a2006-08-04 18:27:19 +00002806/*
2807 * Configure the AEC settings of the sound port.
2808 */
Benny Prijono5da50432006-08-07 10:24:52 +00002809PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00002810{
2811 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2812
2813 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00002814 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
2815 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00002816
2817 return PJ_SUCCESS;
2818}
2819
2820
2821/*
2822 * Get current AEC tail length.
2823 */
Benny Prijono22dfe592006-08-06 12:07:13 +00002824PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00002825{
2826 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2827 return PJ_SUCCESS;
2828}
2829
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002830
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002831/*
Benny Prijonof798e502009-03-09 13:08:16 +00002832 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002833 */
Benny Prijonof798e502009-03-09 13:08:16 +00002834PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002835{
Benny Prijonof798e502009-03-09 13:08:16 +00002836 return pjsua_var.snd_port != NULL;
2837}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002838
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002839
Benny Prijonof798e502009-03-09 13:08:16 +00002840/*
2841 * Configure sound device setting to the sound device being used.
2842 */
2843PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
2844 const void *pval,
2845 pj_bool_t keep)
2846{
Benny Prijono09b0ff62009-03-10 12:07:51 +00002847 pj_status_t status;
2848
Benny Prijonof798e502009-03-09 13:08:16 +00002849 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00002850 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00002851 return PJMEDIA_EAUD_INVCAP;
2852 }
2853
Benny Prijono09b0ff62009-03-10 12:07:51 +00002854 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00002855 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00002856 pjmedia_aud_stream *strm;
2857
2858 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00002859 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00002860 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00002861 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00002862 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00002863
2864 if (status != PJ_SUCCESS)
2865 return status;
2866
2867 /* Save in internal param for later device open */
2868 if (keep) {
2869 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
2870 cap, pval);
2871 }
2872
2873 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00002874}
2875
2876/*
2877 * Retrieve a sound device setting.
2878 */
2879PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
2880 void *pval)
2881{
2882 /* If sound device has never been opened before, open it to
2883 * retrieve the initial setting from the device (e.g. audio
2884 * volume)
2885 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00002886 if (pjsua_var.aud_open_cnt==0) {
2887 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00002888 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00002889 close_snd_dev();
2890 }
Benny Prijonof798e502009-03-09 13:08:16 +00002891
2892 if (pjsua_snd_is_active()) {
2893 /* Sound is active, retrieve from device directly */
2894 pjmedia_aud_stream *strm;
2895
2896 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2897 return pjmedia_aud_stream_get_cap(strm, cap, pval);
2898 } else {
2899 /* Otherwise retrieve from internal param */
2900 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
2901 cap, pval);
2902 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002903}
2904
2905
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002906/*****************************************************************************
2907 * Codecs.
2908 */
2909
2910/*
2911 * Enum all supported codecs in the system.
2912 */
2913PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
2914 unsigned *p_count )
2915{
2916 pjmedia_codec_mgr *codec_mgr;
2917 pjmedia_codec_info info[32];
2918 unsigned i, count, prio[32];
2919 pj_status_t status;
2920
2921 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2922 count = PJ_ARRAY_SIZE(info);
2923 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
2924 if (status != PJ_SUCCESS) {
2925 *p_count = 0;
2926 return status;
2927 }
2928
2929 if (count > *p_count) count = *p_count;
2930
2931 for (i=0; i<count; ++i) {
2932 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
2933 id[i].codec_id = pj_str(id[i].buf_);
2934 id[i].priority = (pj_uint8_t) prio[i];
2935 }
2936
2937 *p_count = count;
2938
2939 return PJ_SUCCESS;
2940}
2941
2942
2943/*
2944 * Change codec priority.
2945 */
2946PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
2947 pj_uint8_t priority )
2948{
Benny Prijono88accae2008-06-26 15:48:14 +00002949 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002950 pjmedia_codec_mgr *codec_mgr;
2951
2952 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2953
Benny Prijono88accae2008-06-26 15:48:14 +00002954 if (codec_id->slen==1 && *codec_id->ptr=='*')
2955 codec_id = &all;
2956
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002957 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
2958 priority);
2959}
2960
2961
2962/*
2963 * Get codec parameters.
2964 */
2965PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
2966 pjmedia_codec_param *param )
2967{
Benny Prijono88accae2008-06-26 15:48:14 +00002968 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002969 const pjmedia_codec_info *info;
2970 pjmedia_codec_mgr *codec_mgr;
2971 unsigned count = 1;
2972 pj_status_t status;
2973
2974 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2975
Benny Prijono88accae2008-06-26 15:48:14 +00002976 if (codec_id->slen==1 && *codec_id->ptr=='*')
2977 codec_id = &all;
2978
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002979 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
2980 &count, &info, NULL);
2981 if (status != PJ_SUCCESS)
2982 return status;
2983
2984 if (count != 1)
2985 return PJ_ENOTFOUND;
2986
2987 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
2988 return status;
2989}
2990
2991
2992/*
2993 * Set codec parameters.
2994 */
2995PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
2996 const pjmedia_codec_param *param)
2997{
Benny Prijono00cae612006-07-31 15:19:36 +00002998 PJ_UNUSED_ARG(id);
2999 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003000 PJ_TODO(set_codec_param);
3001 return PJ_SUCCESS;
3002}