blob: 5496df9ff38cbd69731ac25755172dc5c1aec1b2 [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 */
197 status = pjmedia_codec_passthrough_init(pjsua_var.med_endpt);
198 if (status != PJ_SUCCESS) {
199 pjsua_perror(THIS_FILE, "Error initializing passthrough codecs",
200 status);
201 return status;
202 }
203#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
204
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000205#if PJMEDIA_HAS_G7221_CODEC
206 /* Register G722.1 codecs */
207 status = pjmedia_codec_g7221_init(pjsua_var.med_endpt);
208 if (status != PJ_SUCCESS) {
209 pjsua_perror(THIS_FILE, "Error initializing G722.1 codec",
210 status);
211 return status;
212 }
213#endif /* PJMEDIA_HAS_G7221_CODEC */
214
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000215#if PJMEDIA_HAS_L16_CODEC
216 /* Register L16 family codecs, but disable all */
217 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
218 if (status != PJ_SUCCESS) {
219 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
220 status);
221 return status;
222 }
223
224 /* Disable ALL L16 codecs */
225 codec_id = pj_str("L16");
226 pjmedia_codec_mgr_set_codec_priority(
227 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
228 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
229
230#endif /* PJMEDIA_HAS_L16_CODEC */
231
232
233 /* Save additional conference bridge parameters for future
234 * reference.
235 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000236 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000237 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000238 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
239 pjsua_var.mconf_cfg.channel_count *
240 pjsua_var.media_cfg.audio_frame_ptime /
241 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000242
Benny Prijono0498d902006-06-19 14:49:14 +0000243 /* Init options for conference bridge. */
244 opt = PJMEDIA_CONF_NO_DEVICE;
245 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000246 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000247 {
248 opt |= PJMEDIA_CONF_SMALL_FILTER;
249 }
250 else if (pjsua_var.media_cfg.quality < 3) {
251 opt |= PJMEDIA_CONF_USE_LINEAR;
252 }
253
254
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000255 /* Init conference bridge. */
256 status = pjmedia_conf_create(pjsua_var.pool,
257 pjsua_var.media_cfg.max_media_ports,
258 pjsua_var.media_cfg.clock_rate,
259 pjsua_var.mconf_cfg.channel_count,
260 pjsua_var.mconf_cfg.samples_per_frame,
261 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000262 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000263 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000264 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000265 status);
266 return status;
267 }
268
Benny Prijonof798e502009-03-09 13:08:16 +0000269 /* Are we using the audio switchboard (a.k.a APS-Direct)? */
270 pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
271 ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
272
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000273 /* Create null port just in case user wants to use null sound. */
274 status = pjmedia_null_port_create(pjsua_var.pool,
275 pjsua_var.media_cfg.clock_rate,
276 pjsua_var.mconf_cfg.channel_count,
277 pjsua_var.mconf_cfg.samples_per_frame,
278 pjsua_var.mconf_cfg.bits_per_sample,
279 &pjsua_var.null_port);
280 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
281
Nanang Izzuddin69b69ae2009-04-14 15:18:30 +0000282#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
283 /* Initialize SRTP library. */
284 status = pjmedia_srtp_init_lib();
285 if (status != PJ_SUCCESS) {
286 pjsua_perror(THIS_FILE, "Error initializing SRTP library",
287 status);
288 return status;
289 }
290#endif
291
Benny Prijono6ba8c542007-10-16 01:34:14 +0000292 /* Perform NAT detection */
293 pjsua_detect_nat_type();
294
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000295 return PJ_SUCCESS;
296}
297
298
299/*
300 * Create RTP and RTCP socket pair, and possibly resolve their public
301 * address via STUN.
302 */
303static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
304 pjmedia_sock_info *skinfo)
305{
306 enum {
307 RTP_RETRY = 100
308 };
309 int i;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000310 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000311 pj_sockaddr_in mapped_addr[2];
312 pj_status_t status = PJ_SUCCESS;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000313 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000314 pj_sock_t sock[2];
315
Benny Prijonoc97608e2007-03-23 16:34:20 +0000316 /* Make sure STUN server resolution has completed */
317 status = pjsua_resolve_stun_server(PJ_TRUE);
318 if (status != PJ_SUCCESS) {
319 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
320 return status;
321 }
322
Benny Prijonode479562007-03-15 10:23:55 +0000323 if (next_rtp_port == 0)
324 next_rtp_port = (pj_uint16_t)cfg->port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000325
326 for (i=0; i<2; ++i)
327 sock[i] = PJ_INVALID_SOCKET;
328
Benny Prijono0a5cad82006-09-26 13:21:02 +0000329 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
330 if (cfg->bound_addr.slen) {
331 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
332 if (status != PJ_SUCCESS) {
333 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
334 status);
335 return status;
336 }
337 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000338
339 /* Loop retry to bind RTP and RTCP sockets. */
Benny Prijonode479562007-03-15 10:23:55 +0000340 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000341
342 /* Create and bind RTP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000343 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000344 if (status != PJ_SUCCESS) {
345 pjsua_perror(THIS_FILE, "socket() error", status);
346 return status;
347 }
348
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000349 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
350 next_rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000351 if (status != PJ_SUCCESS) {
352 pj_sock_close(sock[0]);
353 sock[0] = PJ_INVALID_SOCKET;
354 continue;
355 }
356
357 /* Create and bind RTCP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000358 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000359 if (status != PJ_SUCCESS) {
360 pjsua_perror(THIS_FILE, "socket() error", status);
361 pj_sock_close(sock[0]);
362 return status;
363 }
364
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000365 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
366 (pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000367 if (status != PJ_SUCCESS) {
368 pj_sock_close(sock[0]);
369 sock[0] = PJ_INVALID_SOCKET;
370
371 pj_sock_close(sock[1]);
372 sock[1] = PJ_INVALID_SOCKET;
373 continue;
374 }
375
376 /*
377 * If we're configured to use STUN, then find out the mapped address,
378 * and make sure that the mapped RTCP port is adjacent with the RTP.
379 */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000380 if (pjsua_var.stun_srv.addr.sa_family != 0) {
381 char ip_addr[32];
382 pj_str_t stun_srv;
383
384 pj_ansi_strcpy(ip_addr,
385 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
386 stun_srv = pj_str(ip_addr);
387
Benny Prijono14c2b862007-02-21 00:40:05 +0000388 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000389 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
390 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000391 mapped_addr);
392 if (status != PJ_SUCCESS) {
393 pjsua_perror(THIS_FILE, "STUN resolve error", status);
394 goto on_error;
395 }
396
Benny Prijono80eee892007-11-03 22:43:23 +0000397#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000398 if (pj_ntohs(mapped_addr[1].sin_port) ==
399 pj_ntohs(mapped_addr[0].sin_port)+1)
400 {
401 /* Success! */
402 break;
403 }
404
405 pj_sock_close(sock[0]);
406 sock[0] = PJ_INVALID_SOCKET;
407
408 pj_sock_close(sock[1]);
409 sock[1] = PJ_INVALID_SOCKET;
Benny Prijono80eee892007-11-03 22:43:23 +0000410#else
411 if (pj_ntohs(mapped_addr[1].sin_port) !=
412 pj_ntohs(mapped_addr[0].sin_port)+1)
413 {
414 PJ_LOG(4,(THIS_FILE,
415 "Note: STUN mapped RTCP port %d is not adjacent"
416 " to RTP port %d",
417 pj_ntohs(mapped_addr[1].sin_port),
418 pj_ntohs(mapped_addr[0].sin_port)));
419 }
420 /* Success! */
421 break;
422#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000423
Benny Prijono0a5cad82006-09-26 13:21:02 +0000424 } else if (cfg->public_addr.slen) {
425
426 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000427 (pj_uint16_t)next_rtp_port);
Benny Prijono0a5cad82006-09-26 13:21:02 +0000428 if (status != PJ_SUCCESS)
429 goto on_error;
430
431 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000432 (pj_uint16_t)(next_rtp_port+1));
Benny Prijono0a5cad82006-09-26 13:21:02 +0000433 if (status != PJ_SUCCESS)
434 goto on_error;
435
436 break;
437
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000438 } else {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000439
Benny Prijono42d08d22007-12-20 11:23:07 +0000440 if (bound_addr.sin_addr.s_addr == 0) {
441 pj_sockaddr addr;
442
443 /* Get local IP address. */
444 status = pj_gethostip(pj_AF_INET(), &addr);
445 if (status != PJ_SUCCESS)
446 goto on_error;
447
448 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
449 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000450
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000451 for (i=0; i<2; ++i) {
452 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
Benny Prijono42d08d22007-12-20 11:23:07 +0000453 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000454 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000455
Benny Prijonode479562007-03-15 10:23:55 +0000456 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
457 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000458 break;
459 }
460 }
461
462 if (sock[0] == PJ_INVALID_SOCKET) {
463 PJ_LOG(1,(THIS_FILE,
464 "Unable to find appropriate RTP/RTCP ports combination"));
465 goto on_error;
466 }
467
468
469 skinfo->rtp_sock = sock[0];
470 pj_memcpy(&skinfo->rtp_addr_name,
471 &mapped_addr[0], sizeof(pj_sockaddr_in));
472
473 skinfo->rtcp_sock = sock[1];
474 pj_memcpy(&skinfo->rtcp_addr_name,
475 &mapped_addr[1], sizeof(pj_sockaddr_in));
476
Benny Prijono8b22ce12008-02-08 12:57:55 +0000477 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000478 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
479 sizeof(addr_buf), 3)));
Benny Prijono8b22ce12008-02-08 12:57:55 +0000480 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000481 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
482 sizeof(addr_buf), 3)));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000483
Benny Prijonode479562007-03-15 10:23:55 +0000484 next_rtp_port += 2;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000485 return PJ_SUCCESS;
486
487on_error:
488 for (i=0; i<2; ++i) {
489 if (sock[i] != PJ_INVALID_SOCKET)
490 pj_sock_close(sock[i]);
491 }
492 return status;
493}
494
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000495/* Check if sound device is idle. */
496static void check_snd_dev_idle()
497{
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000498 unsigned call_cnt;
499
500 /* Get the call count, we shouldn't close the sound device when there is
501 * any calls active.
502 */
503 call_cnt = pjsua_call_get_count();
504
505 /* When this function is called from pjsua_media_channel_deinit() upon
506 * disconnecting call, actually the call count hasn't been updated/
507 * decreased. So we put additional check here, if there is only one
508 * call and it's in DISCONNECTED state, there is actually no active
509 * call.
510 */
511 if (call_cnt == 1) {
512 pjsua_call_id call_id;
513 pj_status_t status;
514
515 status = pjsua_enum_calls(&call_id, &call_cnt);
516 if (status == PJ_SUCCESS && call_cnt > 0 &&
517 !pjsua_call_is_active(call_id))
518 {
519 call_cnt = 0;
520 }
521 }
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000522
523 /* Activate sound device auto-close timer if sound device is idle.
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000524 * It is idle when there is no port connection in the bridge and
525 * there is no active call.
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000526 */
527 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) &&
528 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
529 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000530 call_cnt == 0 &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000531 pjsua_var.media_cfg.snd_auto_close_time >= 0)
532 {
533 pj_time_val delay;
534
535 delay.msec = 0;
536 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
537
538 pjsua_var.snd_idle_timer.id = PJ_TRUE;
539 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
540 &delay);
541 }
542}
543
544
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000545/* Timer callback to close sound device */
546static void close_snd_timer_cb( pj_timer_heap_t *th,
547 pj_timer_entry *entry)
548{
549 PJ_UNUSED_ARG(th);
550
Benny Prijono0f711b42009-05-06 19:08:43 +0000551 PJSUA_LOCK();
552 if (entry->id) {
553 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
554 pjsua_var.media_cfg.snd_auto_close_time));
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000555
Benny Prijono0f711b42009-05-06 19:08:43 +0000556 entry->id = PJ_FALSE;
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000557
Benny Prijono0f711b42009-05-06 19:08:43 +0000558 close_snd_dev();
559 }
560 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000561}
562
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000563
564/*
565 * Start pjsua media subsystem.
566 */
567pj_status_t pjsua_media_subsys_start(void)
568{
569 pj_status_t status;
570
571 /* Create media for calls, if none is specified */
572 if (pjsua_var.calls[0].med_tp == NULL) {
573 pjsua_transport_config transport_cfg;
574
575 /* Create default transport config */
576 pjsua_transport_config_default(&transport_cfg);
577 transport_cfg.port = DEFAULT_RTP_PORT;
578
579 status = pjsua_media_transports_create(&transport_cfg);
580 if (status != PJ_SUCCESS)
581 return status;
582 }
583
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000584 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
585 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000586
587 return PJ_SUCCESS;
588}
589
590
591/*
592 * Destroy pjsua media subsystem.
593 */
594pj_status_t pjsua_media_subsys_destroy(void)
595{
596 unsigned i;
597
598 close_snd_dev();
599
600 if (pjsua_var.mconf) {
601 pjmedia_conf_destroy(pjsua_var.mconf);
602 pjsua_var.mconf = NULL;
603 }
604
605 if (pjsua_var.null_port) {
606 pjmedia_port_destroy(pjsua_var.null_port);
607 pjsua_var.null_port = NULL;
608 }
609
610 /* Destroy file players */
611 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
612 if (pjsua_var.player[i].port) {
613 pjmedia_port_destroy(pjsua_var.player[i].port);
614 pjsua_var.player[i].port = NULL;
615 }
616 }
617
618 /* Destroy file recorders */
619 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
620 if (pjsua_var.recorder[i].port) {
621 pjmedia_port_destroy(pjsua_var.recorder[i].port);
622 pjsua_var.recorder[i].port = NULL;
623 }
624 }
625
626 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000627 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono311b63f2008-07-14 11:31:40 +0000628 if (pjsua_var.calls[i].med_tp_st != PJSUA_MED_TP_IDLE) {
629 pjsua_media_channel_deinit(i);
630 }
Benny Prijono40860c32008-09-04 13:55:33 +0000631 if (pjsua_var.calls[i].med_tp && pjsua_var.calls[i].med_tp_auto_del) {
632 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000633 }
Benny Prijono40860c32008-09-04 13:55:33 +0000634 pjsua_var.calls[i].med_tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000635 }
636
637 /* Destroy media endpoint. */
638 if (pjsua_var.med_endpt) {
639
640 /* Shutdown all codecs: */
641# if PJMEDIA_HAS_SPEEX_CODEC
642 pjmedia_codec_speex_deinit();
643# endif /* PJMEDIA_HAS_SPEEX_CODEC */
644
645# if PJMEDIA_HAS_GSM_CODEC
646 pjmedia_codec_gsm_deinit();
647# endif /* PJMEDIA_HAS_GSM_CODEC */
648
649# if PJMEDIA_HAS_G711_CODEC
650 pjmedia_codec_g711_deinit();
651# endif /* PJMEDIA_HAS_G711_CODEC */
652
Benny Prijono7ffd7752008-03-17 14:07:53 +0000653# if PJMEDIA_HAS_G722_CODEC
654 pjmedia_codec_g722_deinit();
655# endif /* PJMEDIA_HAS_G722_CODEC */
656
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000657# if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000658 pjmedia_codec_ipp_deinit();
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000659# endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000660
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000661# if PJMEDIA_HAS_PASSTHROUGH_CODECS
662 pjmedia_codec_passthrough_deinit();
663# endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
664
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000665# if PJMEDIA_HAS_G7221_CODEC
666 pjmedia_codec_g7221_deinit();
667# endif /* PJMEDIA_HAS_G7221_CODEC */
668
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000669# if PJMEDIA_HAS_L16_CODEC
670 pjmedia_codec_l16_deinit();
671# endif /* PJMEDIA_HAS_L16_CODEC */
672
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000673 pjmedia_endpt_destroy(pjsua_var.med_endpt);
674 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000675
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000676 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000677 // Not necessary, as pjmedia_snd_deinit() should have been called
678 // in pjmedia_endpt_destroy().
679 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000680 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000681
Benny Prijonode479562007-03-15 10:23:55 +0000682 /* Reset RTP port */
683 next_rtp_port = 0;
684
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000685 return PJ_SUCCESS;
686}
687
688
Benny Prijonoc97608e2007-03-23 16:34:20 +0000689/* Create normal UDP media transports */
690static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000691{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000692 unsigned i;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000693 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000694 pj_status_t status;
695
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000696 /* Create each media transport */
697 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
698
Benny Prijono617c5bc2007-04-02 19:51:21 +0000699 status = create_rtp_rtcp_sock(cfg, &skinfo);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000700 if (status != PJ_SUCCESS) {
701 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
702 status);
703 goto on_error;
704 }
Benny Prijonod8179652008-01-23 20:39:07 +0000705
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000706 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000707 &skinfo, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000708 &pjsua_var.calls[i].med_tp);
709 if (status != PJ_SUCCESS) {
710 pjsua_perror(THIS_FILE, "Unable to create media transport",
711 status);
712 goto on_error;
713 }
Benny Prijono00cae612006-07-31 15:19:36 +0000714
Benny Prijonod8179652008-01-23 20:39:07 +0000715 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
716 PJMEDIA_DIR_ENCODING,
717 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000718
Benny Prijonod8179652008-01-23 20:39:07 +0000719 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
720 PJMEDIA_DIR_DECODING,
721 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000722
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000723 }
724
Benny Prijonoc97608e2007-03-23 16:34:20 +0000725 return PJ_SUCCESS;
726
727on_error:
728 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
729 if (pjsua_var.calls[i].med_tp != NULL) {
730 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
731 pjsua_var.calls[i].med_tp = NULL;
732 }
733 }
734
735 return status;
736}
737
738
Benny Prijono096c56c2007-09-15 08:30:16 +0000739/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000740static void on_ice_complete(pjmedia_transport *tp,
741 pj_ice_strans_op op,
742 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000743{
Benny Prijonof76e1392008-06-06 14:51:48 +0000744 unsigned id;
Benny Prijono096c56c2007-09-15 08:30:16 +0000745 pj_bool_t found = PJ_FALSE;
746
Benny Prijono096c56c2007-09-15 08:30:16 +0000747 /* Find call which has this media transport */
748
749 PJSUA_LOCK();
750
Benny Prijonof76e1392008-06-06 14:51:48 +0000751 for (id=0; id<pjsua_var.ua_cfg.max_calls; ++id) {
752 if (pjsua_var.calls[id].med_tp == tp ||
753 pjsua_var.calls[id].med_orig == tp)
754 {
755 found = PJ_TRUE;
756 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000757 }
758 }
759
760 PJSUA_UNLOCK();
761
Benny Prijonof76e1392008-06-06 14:51:48 +0000762 if (!found)
763 return;
764
765 switch (op) {
766 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono224b4e22008-06-19 14:10:28 +0000767 pjsua_var.calls[id].med_tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000768 break;
769 case PJ_ICE_STRANS_OP_NEGOTIATION:
770 if (result != PJ_SUCCESS) {
771 pjsua_var.calls[id].media_st = PJSUA_CALL_MEDIA_ERROR;
772 pjsua_var.calls[id].media_dir = PJMEDIA_DIR_NONE;
773
774 if (pjsua_var.ua_cfg.cb.on_call_media_state) {
775 pjsua_var.ua_cfg.cb.on_call_media_state(id);
776 }
777 }
778 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000779 }
780}
781
782
Benny Prijonof76e1392008-06-06 14:51:48 +0000783/* Parse "HOST:PORT" format */
784static pj_status_t parse_host_port(const pj_str_t *host_port,
785 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000786{
Benny Prijonof76e1392008-06-06 14:51:48 +0000787 pj_str_t str_port;
788
789 str_port.ptr = pj_strchr(host_port, ':');
790 if (str_port.ptr != NULL) {
791 int iport;
792
793 host->ptr = host_port->ptr;
794 host->slen = (str_port.ptr - host->ptr);
795 str_port.ptr++;
796 str_port.slen = host_port->slen - host->slen - 1;
797 iport = (int)pj_strtoul(&str_port);
798 if (iport < 1 || iport > 65535)
799 return PJ_EINVAL;
800 *port = (pj_uint16_t)iport;
801 } else {
802 *host = *host_port;
803 *port = 0;
804 }
805
806 return PJ_SUCCESS;
807}
808
809/* Create ICE media transports (when ice is enabled) */
810static pj_status_t create_ice_media_transports(void)
811{
812 char stunip[PJ_INET6_ADDRSTRLEN];
813 pj_ice_strans_cfg ice_cfg;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000814 unsigned i;
815 pj_status_t status;
816
Benny Prijonoda9785b2007-04-02 20:43:06 +0000817 /* Make sure STUN server resolution has completed */
818 status = pjsua_resolve_stun_server(PJ_TRUE);
819 if (status != PJ_SUCCESS) {
820 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
821 return status;
822 }
823
Benny Prijonof76e1392008-06-06 14:51:48 +0000824 /* Create ICE stream transport configuration */
825 pj_ice_strans_cfg_default(&ice_cfg);
826 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
827 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
828 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
829
830 ice_cfg.af = pj_AF_INET();
831 ice_cfg.resolver = pjsua_var.resolver;
832
Benny Prijono329d6382009-05-29 13:04:03 +0000833 ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
834
Benny Prijonof76e1392008-06-06 14:51:48 +0000835 /* Configure STUN settings */
836 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
837 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
838 ice_cfg.stun.server = pj_str(stunip);
839 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
840 }
Benny Prijono329d6382009-05-29 13:04:03 +0000841 if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
842 ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
Benny Prijonof76e1392008-06-06 14:51:48 +0000843
844 /* Configure TURN settings */
845 if (pjsua_var.media_cfg.enable_turn) {
846 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
847 &ice_cfg.turn.server,
848 &ice_cfg.turn.port);
849 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
850 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
851 return PJ_EINVAL;
852 }
853 if (ice_cfg.turn.port == 0)
854 ice_cfg.turn.port = 3479;
855 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
856 pj_memcpy(&ice_cfg.turn.auth_cred,
857 &pjsua_var.media_cfg.turn_auth_cred,
858 sizeof(ice_cfg.turn.auth_cred));
859 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000860
Benny Prijonoc97608e2007-03-23 16:34:20 +0000861 /* Create each media transport */
862 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono096c56c2007-09-15 08:30:16 +0000863 pjmedia_ice_cb ice_cb;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000864 char name[32];
Benny Prijonof76e1392008-06-06 14:51:48 +0000865 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000866
Benny Prijono096c56c2007-09-15 08:30:16 +0000867 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
868 ice_cb.on_ice_complete = &on_ice_complete;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000869 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i);
Benny Prijono224b4e22008-06-19 14:10:28 +0000870 pjsua_var.calls[i].med_tp_ready = PJ_EPENDING;
Benny Prijonof76e1392008-06-06 14:51:48 +0000871
872 comp_cnt = 1;
Benny Prijono551af422008-08-07 09:55:52 +0000873 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
Benny Prijonof76e1392008-06-06 14:51:48 +0000874 ++comp_cnt;
875
876 status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
877 &ice_cfg, &ice_cb,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000878 &pjsua_var.calls[i].med_tp);
879 if (status != PJ_SUCCESS) {
880 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
881 status);
882 goto on_error;
883 }
884
Benny Prijonof76e1392008-06-06 14:51:48 +0000885 /* Wait until transport is initialized, or time out */
886 PJSUA_UNLOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000887 while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000888 pjsua_handle_events(100);
889 }
890 PJSUA_LOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000891 if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000892 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
Benny Prijono224b4e22008-06-19 14:10:28 +0000893 pjsua_var.calls[i].med_tp_ready);
894 status = pjsua_var.calls[i].med_tp_ready;
Benny Prijonof76e1392008-06-06 14:51:48 +0000895 goto on_error;
896 }
897
Benny Prijonod8179652008-01-23 20:39:07 +0000898 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
899 PJMEDIA_DIR_ENCODING,
900 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono11da9bc2007-09-15 08:55:00 +0000901
Benny Prijonod8179652008-01-23 20:39:07 +0000902 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
903 PJMEDIA_DIR_DECODING,
904 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000905 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000906
907 return PJ_SUCCESS;
908
909on_error:
910 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
911 if (pjsua_var.calls[i].med_tp != NULL) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000912 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000913 pjsua_var.calls[i].med_tp = NULL;
914 }
915 }
916
Benny Prijonoc97608e2007-03-23 16:34:20 +0000917 return status;
918}
919
920
921/*
922 * Create UDP media transports for all the calls. This function creates
923 * one UDP media transport for each call.
924 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +0000925PJ_DEF(pj_status_t) pjsua_media_transports_create(
926 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000927{
928 pjsua_transport_config cfg;
929 unsigned i;
930 pj_status_t status;
931
932
933 /* Make sure pjsua_init() has been called */
934 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
935
936 PJSUA_LOCK();
937
938 /* Delete existing media transports */
939 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono40860c32008-09-04 13:55:33 +0000940 if (pjsua_var.calls[i].med_tp != NULL &&
941 pjsua_var.calls[i].med_tp_auto_del)
942 {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000943 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
944 pjsua_var.calls[i].med_tp = NULL;
Nanang Izzuddind704a8b2008-09-23 16:34:07 +0000945 pjsua_var.calls[i].med_orig = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000946 }
947 }
948
949 /* Copy config */
950 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
951
Benny Prijono40860c32008-09-04 13:55:33 +0000952 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000953 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000954 status = create_ice_media_transports();
Benny Prijonoc97608e2007-03-23 16:34:20 +0000955 } else {
956 status = create_udp_media_transports(&cfg);
957 }
958
Benny Prijono40860c32008-09-04 13:55:33 +0000959 /* Set media transport auto_delete to True */
960 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
961 pjsua_var.calls[i].med_tp_auto_del = PJ_TRUE;
962 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000963
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000964 PJSUA_UNLOCK();
965
966 return status;
967}
968
Benny Prijono40860c32008-09-04 13:55:33 +0000969/*
970 * Attach application's created media transports.
971 */
972PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
973 unsigned count,
974 pj_bool_t auto_delete)
975{
976 unsigned i;
977
978 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
979
980 /* Assign the media transports */
981 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
982 if (pjsua_var.calls[i].med_tp != NULL &&
983 pjsua_var.calls[i].med_tp_auto_del)
984 {
985 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
986 }
987
988 pjsua_var.calls[i].med_tp = tp[i].transport;
989 pjsua_var.calls[i].med_tp_auto_del = auto_delete;
990 }
991
992 return PJ_SUCCESS;
993}
994
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000995
Benny Prijonoc97608e2007-03-23 16:34:20 +0000996pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
Benny Prijonod8179652008-01-23 20:39:07 +0000997 pjsip_role_e role,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000998 int security_level,
Benny Prijono224b4e22008-06-19 14:10:28 +0000999 pj_pool_t *tmp_pool,
1000 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001001 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001002{
1003 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono224b4e22008-06-19 14:10:28 +00001004 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001005
Benny Prijonod8179652008-01-23 20:39:07 +00001006#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1007 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
1008 pjmedia_srtp_setting srtp_opt;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001009 pjmedia_transport *srtp = NULL;
Benny Prijonod8179652008-01-23 20:39:07 +00001010#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +00001011
Benny Prijonod8179652008-01-23 20:39:07 +00001012 PJ_UNUSED_ARG(role);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001013
Benny Prijonod8179652008-01-23 20:39:07 +00001014 /* Return error if media transport has not been created yet
1015 * (e.g. application is starting)
1016 */
1017 if (call->med_tp == NULL) {
Benny Prijono03789052008-09-16 14:30:50 +00001018 if (sip_err_code)
1019 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
Benny Prijonod8179652008-01-23 20:39:07 +00001020 return PJ_EBUSY;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001021 }
1022
Benny Prijonod8179652008-01-23 20:39:07 +00001023#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Benny Prijono53a7c702008-04-14 02:57:29 +00001024 /* This function may be called when SRTP transport already exists
1025 * (e.g: in re-invite, update), don't need to destroy/re-create.
1026 */
1027 if (!call->med_orig || call->med_tp == call->med_orig) {
1028
1029 /* Check if SRTP requires secure signaling */
1030 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
1031 if (security_level < acc->cfg.srtp_secure_signaling) {
1032 if (sip_err_code)
1033 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1034 return PJSIP_ESESSIONINSECURE;
1035 }
Benny Prijonod8179652008-01-23 20:39:07 +00001036 }
Benny Prijonod8179652008-01-23 20:39:07 +00001037
Benny Prijono53a7c702008-04-14 02:57:29 +00001038 /* Always create SRTP adapter */
1039 pjmedia_srtp_setting_default(&srtp_opt);
1040 srtp_opt.close_member_tp = PJ_FALSE;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001041 /* If media session has been ever established, let's use remote's
1042 * preference in SRTP usage policy, especially when it is stricter.
1043 */
1044 if (call->rem_srtp_use > acc->cfg.use_srtp)
1045 srtp_opt.use = call->rem_srtp_use;
1046 else
1047 srtp_opt.use = acc->cfg.use_srtp;
1048
Benny Prijono53a7c702008-04-14 02:57:29 +00001049 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
1050 call->med_tp,
1051 &srtp_opt, &srtp);
1052 if (status != PJ_SUCCESS) {
1053 if (sip_err_code)
1054 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
1055 return status;
1056 }
Benny Prijonod8179652008-01-23 20:39:07 +00001057
Benny Prijono53a7c702008-04-14 02:57:29 +00001058 /* Set SRTP as current media transport */
1059 call->med_orig = call->med_tp;
1060 call->med_tp = srtp;
1061 }
Benny Prijonod8179652008-01-23 20:39:07 +00001062#else
1063 call->med_orig = call->med_tp;
1064 PJ_UNUSED_ARG(security_level);
1065#endif
1066
Benny Prijonoa310bd22008-06-27 21:19:44 +00001067 /* Find out which media line in SDP that we support. If we are offerer,
1068 * audio will be at index 0 in SDP.
1069 */
1070 if (rem_sdp == 0) {
1071 call->audio_idx = 0;
1072 }
1073 /* Otherwise find out the candidate audio media line in SDP */
1074 else {
1075 unsigned i;
1076 pj_bool_t srtp_active;
1077
1078#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1079 srtp_active = acc->cfg.use_srtp && srtp != NULL;
1080#else
1081 srtp_active = PJ_FALSE;
1082#endif
1083
1084 /* Media count must have been checked */
1085 pj_assert(rem_sdp->media_count != 0);
1086
1087 for (i=0; i<rem_sdp->media_count; ++i) {
1088 const pjmedia_sdp_media *m = rem_sdp->media[i];
1089
1090 /* Skip if media is not audio */
1091 if (pj_stricmp2(&m->desc.media, "audio") != 0)
1092 continue;
1093
1094 /* Skip if media is disabled */
1095 if (m->desc.port == 0)
1096 continue;
1097
1098 /* Skip if transport is not supported */
1099 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") != 0 &&
1100 pj_stricmp2(&m->desc.transport, "RTP/SAVP") != 0)
1101 {
1102 continue;
1103 }
1104
1105 if (call->audio_idx == -1) {
1106 call->audio_idx = i;
1107 } else {
1108 /* We've found multiple candidates. This could happen
1109 * e.g. when remote is offering both RTP/AVP and RTP/AVP,
1110 * or when remote for some reason offers two audio.
1111 */
1112
1113 if (srtp_active &&
1114 pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0)
1115 {
1116 /* Prefer RTP/SAVP when our media transport is SRTP */
1117 call->audio_idx = i;
1118 } else if (!srtp_active &&
1119 pj_stricmp2(&m->desc.transport, "RTP/AVP")==0)
1120 {
1121 /* Prefer RTP/AVP when our media transport is NOT SRTP */
1122 call->audio_idx = i;
1123 }
1124 }
1125 }
1126 }
1127
1128 /* Reject offer if we couldn't find a good m=audio line in offer */
1129 if (call->audio_idx < 0) {
Benny Prijonoab8dba92008-06-27 21:59:15 +00001130 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001131 pjsua_media_channel_deinit(call_id);
Benny Prijonoab8dba92008-06-27 21:59:15 +00001132 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001133 }
1134
1135 PJ_LOG(4,(THIS_FILE, "Media index %d selected for call %d",
1136 call->audio_idx, call->index));
1137
Benny Prijono224b4e22008-06-19 14:10:28 +00001138 /* Create the media transport */
1139 status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001140 rem_sdp, call->audio_idx);
Benny Prijono224b4e22008-06-19 14:10:28 +00001141 if (status != PJ_SUCCESS) {
1142 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1143 pjsua_media_channel_deinit(call_id);
1144 return status;
1145 }
1146
1147 call->med_tp_st = PJSUA_MED_TP_INIT;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001148 return PJ_SUCCESS;
1149}
1150
1151pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1152 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001153 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001154 pjmedia_sdp_session **p_sdp,
1155 int *sip_status_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001156{
Benny Prijonoa310bd22008-06-27 21:19:44 +00001157 enum { MAX_MEDIA = 1 };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001158 pjmedia_sdp_session *sdp;
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001159 pjmedia_transport_info tpinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001160 pjsua_call *call = &pjsua_var.calls[call_id];
1161 pj_status_t status;
1162
Benny Prijono55e82352007-05-10 20:49:08 +00001163 /* Return error if media transport has not been created yet
1164 * (e.g. application is starting)
1165 */
1166 if (call->med_tp == NULL) {
1167 return PJ_EBUSY;
1168 }
1169
Benny Prijonoa310bd22008-06-27 21:19:44 +00001170 /* Media index must have been determined before */
1171 pj_assert(call->audio_idx != -1);
1172
Benny Prijono224b4e22008-06-19 14:10:28 +00001173 /* Create media if it's not created. This could happen when call is
1174 * currently on-hold
1175 */
1176 if (call->med_tp_st == PJSUA_MED_TP_IDLE) {
1177 pjsip_role_e role;
1178 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1179 status = pjsua_media_channel_init(call_id, role, call->secure_level,
1180 pool, rem_sdp, sip_status_code);
1181 if (status != PJ_SUCCESS)
1182 return status;
1183 }
1184
Benny Prijono617c5bc2007-04-02 19:51:21 +00001185 /* Get media socket info */
Benny Prijono734fc2d2008-03-17 16:05:35 +00001186 pjmedia_transport_info_init(&tpinfo);
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001187 pjmedia_transport_get_info(call->med_tp, &tpinfo);
Benny Prijono617c5bc2007-04-02 19:51:21 +00001188
1189 /* Create SDP */
Benny Prijonod8179652008-01-23 20:39:07 +00001190 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA,
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001191 &tpinfo.sock_info, &sdp);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001192 if (status != PJ_SUCCESS) {
1193 if (sip_status_code) *sip_status_code = 500;
Benny Prijono224b4e22008-06-19 14:10:28 +00001194 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001195 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001196
Benny Prijonoa310bd22008-06-27 21:19:44 +00001197 /* If we're answering and the selected media is not the first media
1198 * in SDP, then fill in the unselected media with with zero port.
1199 * Otherwise we'll crash in transport_encode_sdp() because the media
1200 * lines are not aligned between offer and answer.
1201 */
1202 if (rem_sdp && call->audio_idx != 0) {
1203 unsigned i;
1204
1205 for (i=0; i<rem_sdp->media_count; ++i) {
1206 const pjmedia_sdp_media *rem_m = rem_sdp->media[i];
1207 pjmedia_sdp_media *m;
1208 const pjmedia_sdp_attr *a;
1209
1210 if ((int)i == call->audio_idx)
1211 continue;
1212
1213 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1214 pj_strdup(pool, &m->desc.media, &rem_m->desc.media);
1215 pj_strdup(pool, &m->desc.transport, &rem_m->desc.transport);
1216 m->desc.port = 0;
1217
1218 /* Add one format, copy from the offer. And copy the corresponding
1219 * rtpmap and fmtp attributes too.
1220 */
1221 m->desc.fmt_count = 1;
1222 pj_strdup(pool, &m->desc.fmt[0], &rem_m->desc.fmt[0]);
1223 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1224 "rtpmap", &m->desc.fmt[0])) != NULL)
1225 {
1226 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1227 }
1228 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1229 "fmtp", &m->desc.fmt[0])) != NULL)
1230 {
1231 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1232 }
1233
1234 if (i==sdp->media_count)
1235 sdp->media[sdp->media_count++] = m;
1236 else {
1237 pj_array_insert(sdp->media, sizeof(sdp->media[0]),
1238 sdp->media_count, i, &m);
1239 ++sdp->media_count;
1240 }
1241 }
1242 }
1243
Benny Prijono6ba8c542007-10-16 01:34:14 +00001244 /* Add NAT info in the SDP */
1245 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1246 pjmedia_sdp_attr *a;
1247 pj_str_t value;
1248 char nat_info[80];
1249
1250 value.ptr = nat_info;
1251 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1252 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1253 "%d", pjsua_var.nat_type);
1254 } else {
1255 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1256 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1257 "%d %s",
1258 pjsua_var.nat_type,
1259 type_name);
1260 }
1261
1262 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1263
1264 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1265
1266 }
1267
Benny Prijonod8179652008-01-23 20:39:07 +00001268 /* Give the SDP to media transport */
Benny Prijono224b4e22008-06-19 14:10:28 +00001269 status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001270 call->audio_idx);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001271 if (status != PJ_SUCCESS) {
1272 if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono224b4e22008-06-19 14:10:28 +00001273 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001274 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001275
1276 *p_sdp = sdp;
1277 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001278}
1279
1280
1281static void stop_media_session(pjsua_call_id call_id)
1282{
1283 pjsua_call *call = &pjsua_var.calls[call_id];
1284
1285 if (call->conf_slot != PJSUA_INVALID_ID) {
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001286 if (pjsua_var.mconf) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001287 pjsua_conf_remove_port(call->conf_slot);
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001288 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001289 call->conf_slot = PJSUA_INVALID_ID;
1290 }
1291
1292 if (call->session) {
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001293 pjmedia_rtcp_stat stat;
1294
Nanang Izzuddin437d77c2008-08-26 18:04:15 +00001295 if (pjmedia_session_get_stream_stat(call->session, 0, &stat)
1296 == PJ_SUCCESS)
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001297 {
1298 /* Save RTP timestamp & sequence, so when media session is
1299 * restarted, those values will be restored as the initial
1300 * RTP timestamp & sequence of the new media session. So in
1301 * the same call session, RTP timestamp and sequence are
1302 * guaranteed to be contigue.
1303 */
1304 call->rtp_tx_seq_ts_set = 1 | (1 << 1);
1305 call->rtp_tx_seq = stat.rtp_tx_last_seq;
1306 call->rtp_tx_ts = stat.rtp_tx_last_ts;
1307 }
1308
Benny Prijonofc13bf62008-02-20 08:56:15 +00001309 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1310 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0);
1311 }
1312
Benny Prijonoc97608e2007-03-23 16:34:20 +00001313 pjmedia_session_destroy(call->session);
1314 call->session = NULL;
1315
1316 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
1317 call_id));
1318
1319 }
1320
1321 call->media_st = PJSUA_CALL_MEDIA_NONE;
1322}
1323
1324pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1325{
1326 pjsua_call *call = &pjsua_var.calls[call_id];
1327
1328 stop_media_session(call_id);
1329
Benny Prijono224b4e22008-06-19 14:10:28 +00001330 if (call->med_tp_st != PJSUA_MED_TP_IDLE) {
1331 pjmedia_transport_media_stop(call->med_tp);
1332 call->med_tp_st = PJSUA_MED_TP_IDLE;
1333 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001334
Benny Prijono311b63f2008-07-14 11:31:40 +00001335 if (call->med_orig && call->med_tp && call->med_tp != call->med_orig) {
Benny Prijonod8179652008-01-23 20:39:07 +00001336 pjmedia_transport_close(call->med_tp);
1337 call->med_tp = call->med_orig;
1338 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00001339
1340 check_snd_dev_idle();
1341
Benny Prijonoc97608e2007-03-23 16:34:20 +00001342 return PJ_SUCCESS;
1343}
1344
1345
1346/*
1347 * DTMF callback from the stream.
1348 */
1349static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1350 int digit)
1351{
1352 PJ_UNUSED_ARG(strm);
1353
Benny Prijono0c068262008-02-14 14:38:52 +00001354 /* For discussions about call mutex protection related to this
1355 * callback, please see ticket #460:
1356 * http://trac.pjsip.org/repos/ticket/460#comment:4
1357 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001358 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1359 pjsua_call_id call_id;
1360
Benny Prijonod8179652008-01-23 20:39:07 +00001361 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001362 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1363 }
1364}
1365
1366
1367pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijono224b4e22008-06-19 14:10:28 +00001368 const pjmedia_sdp_session *local_sdp,
Benny Prijonodbce2cf2007-03-28 16:24:00 +00001369 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001370{
1371 int prev_media_st = 0;
1372 pjsua_call *call = &pjsua_var.calls[call_id];
1373 pjmedia_session_info sess_info;
Benny Prijono91e567e2007-12-28 08:51:58 +00001374 pjmedia_stream_info *si = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001375 pjmedia_port *media_port;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001376 pj_status_t status;
1377
1378 /* Destroy existing media session, if any. */
1379 prev_media_st = call->media_st;
1380 stop_media_session(call->index);
1381
1382 /* Create media session info based on SDP parameters.
Benny Prijonoc97608e2007-03-23 16:34:20 +00001383 */
1384 status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
1385 pjsua_var.med_endpt,
Benny Prijono91e567e2007-12-28 08:51:58 +00001386 PJMEDIA_MAX_SDP_MEDIA, &sess_info,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001387 local_sdp, remote_sdp);
1388 if (status != PJ_SUCCESS)
1389 return status;
1390
Benny Prijonoa310bd22008-06-27 21:19:44 +00001391 /* Find which session is audio */
1392 PJ_ASSERT_RETURN(call->audio_idx != -1, PJ_EBUG);
1393 PJ_ASSERT_RETURN(call->audio_idx < (int)sess_info.stream_cnt, PJ_EBUG);
1394 si = &sess_info.stream_info[call->audio_idx];
Benny Prijono91e567e2007-12-28 08:51:58 +00001395
1396 /* Reset session info with only one media stream */
1397 sess_info.stream_cnt = 1;
1398 if (si != &sess_info.stream_info[0])
1399 pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001400
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001401 /* Check if no media is active */
Benny Prijono91e567e2007-12-28 08:51:58 +00001402 if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001403 {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001404 /* Call media state */
1405 call->media_st = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001406
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001407 /* Call media direction */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001408 call->media_dir = PJMEDIA_DIR_NONE;
1409
Benny Prijonod8179652008-01-23 20:39:07 +00001410 /* Shutdown transport's session */
1411 pjmedia_transport_media_stop(call->med_tp);
Benny Prijono224b4e22008-06-19 14:10:28 +00001412 call->med_tp_st = PJSUA_MED_TP_IDLE;
Benny Prijono667952e2007-04-02 19:27:54 +00001413
Benny Prijonoc97608e2007-03-23 16:34:20 +00001414 /* No need because we need keepalive? */
1415
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001416 /* Close upper entry of transport stack */
1417 if (call->med_orig && (call->med_tp != call->med_orig)) {
1418 pjmedia_transport_close(call->med_tp);
1419 call->med_tp = call->med_orig;
1420 }
1421
Benny Prijonoc97608e2007-03-23 16:34:20 +00001422 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001423 pjmedia_transport_info tp_info;
1424
Benny Prijono224b4e22008-06-19 14:10:28 +00001425 /* Start/restart media transport */
Benny Prijonod8179652008-01-23 20:39:07 +00001426 status = pjmedia_transport_media_start(call->med_tp,
1427 call->inv->pool,
1428 local_sdp, remote_sdp, 0);
1429 if (status != PJ_SUCCESS)
1430 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001431
Benny Prijono224b4e22008-06-19 14:10:28 +00001432 call->med_tp_st = PJSUA_MED_TP_RUNNING;
1433
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001434 /* Get remote SRTP usage policy */
1435 pjmedia_transport_info_init(&tp_info);
1436 pjmedia_transport_get_info(call->med_tp, &tp_info);
1437 if (tp_info.specific_info_cnt > 0) {
1438 int i;
1439 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1440 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1441 {
1442 pjmedia_srtp_info *srtp_info =
1443 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1444
1445 call->rem_srtp_use = srtp_info->peer_use;
1446 break;
1447 }
1448 }
1449 }
1450
Benny Prijonoc97608e2007-03-23 16:34:20 +00001451 /* Override ptime, if this option is specified. */
1452 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001453 si->param->setting.frm_per_pkt = (pj_uint8_t)
1454 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1455 if (si->param->setting.frm_per_pkt == 0)
1456 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001457 }
1458
1459 /* Disable VAD, if this option is specified. */
1460 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001461 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001462 }
1463
1464
1465 /* Optionally, application may modify other stream settings here
1466 * (such as jitter buffer parameters, codec ptime, etc.)
1467 */
Benny Prijono91e567e2007-12-28 08:51:58 +00001468 si->jb_init = pjsua_var.media_cfg.jb_init;
1469 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1470 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1471 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001472
Benny Prijono8147f402007-11-21 14:50:07 +00001473 /* Set SSRC */
Benny Prijono91e567e2007-12-28 08:51:58 +00001474 si->ssrc = call->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00001475
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001476 /* Set RTP timestamp & sequence, normally these value are intialized
1477 * automatically when stream session created, but for some cases (e.g:
1478 * call reinvite, call update) timestamp and sequence need to be kept
1479 * contigue.
1480 */
1481 si->rtp_ts = call->rtp_tx_ts;
1482 si->rtp_seq = call->rtp_tx_seq;
1483 si->rtp_seq_ts_set = call->rtp_tx_seq_ts_set;
1484
Benny Prijonoc97608e2007-03-23 16:34:20 +00001485 /* Create session based on session info. */
1486 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
1487 &call->med_tp,
1488 call, &call->session );
1489 if (status != PJ_SUCCESS) {
1490 return status;
1491 }
1492
1493 /* If DTMF callback is installed by application, install our
1494 * callback to the session.
1495 */
1496 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1497 pjmedia_session_set_dtmf_callback(call->session, 0,
1498 &dtmf_callback,
Benny Prijonod8179652008-01-23 20:39:07 +00001499 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001500 }
1501
1502 /* Get the port interface of the first stream in the session.
1503 * We need the port interface to add to the conference bridge.
1504 */
1505 pjmedia_session_get_port(call->session, 0, &media_port);
1506
Benny Prijonofc13bf62008-02-20 08:56:15 +00001507 /* Notify application about stream creation.
1508 * Note: application may modify media_port to point to different
1509 * media port
1510 */
1511 if (pjsua_var.ua_cfg.cb.on_stream_created) {
1512 pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session,
1513 0, &media_port);
1514 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001515
1516 /*
1517 * Add the call to conference bridge.
1518 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001519 {
1520 char tmp[PJSIP_MAX_URL_SIZE];
1521 pj_str_t port_name;
1522
1523 port_name.ptr = tmp;
1524 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1525 call->inv->dlg->remote.info->uri,
1526 tmp, sizeof(tmp));
1527 if (port_name.slen < 1) {
1528 port_name = pj_str("call");
1529 }
1530 status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
1531 media_port,
1532 &port_name,
1533 (unsigned*)&call->conf_slot);
1534 if (status != PJ_SUCCESS) {
1535 return status;
1536 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001537 }
1538
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001539 /* Call media direction */
Benny Prijono91e567e2007-12-28 08:51:58 +00001540 call->media_dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001541
1542 /* Call media state */
1543 if (call->local_hold)
1544 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
1545 else if (call->media_dir == PJMEDIA_DIR_DECODING)
1546 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
1547 else
1548 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001549 }
1550
1551 /* Print info. */
1552 {
1553 char info[80];
1554 int info_len = 0;
1555 unsigned i;
1556
1557 for (i=0; i<sess_info.stream_cnt; ++i) {
1558 int len;
1559 const char *dir;
1560 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1561
1562 switch (strm_info->dir) {
1563 case PJMEDIA_DIR_NONE:
1564 dir = "inactive";
1565 break;
1566 case PJMEDIA_DIR_ENCODING:
1567 dir = "sendonly";
1568 break;
1569 case PJMEDIA_DIR_DECODING:
1570 dir = "recvonly";
1571 break;
1572 case PJMEDIA_DIR_ENCODING_DECODING:
1573 dir = "sendrecv";
1574 break;
1575 default:
1576 dir = "unknown";
1577 break;
1578 }
1579 len = pj_ansi_sprintf( info+info_len,
1580 ", stream #%d: %.*s (%s)", i,
1581 (int)strm_info->fmt.encoding_name.slen,
1582 strm_info->fmt.encoding_name.ptr,
1583 dir);
1584 if (len > 0)
1585 info_len += len;
1586 }
1587 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1588 }
1589
1590 return PJ_SUCCESS;
1591}
1592
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001593/*
1594 * Get maxinum number of conference ports.
1595 */
1596PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1597{
1598 return pjsua_var.media_cfg.max_media_ports;
1599}
1600
1601
1602/*
1603 * Get current number of active ports in the bridge.
1604 */
1605PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1606{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001607 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001608 unsigned count = PJ_ARRAY_SIZE(ports);
1609 pj_status_t status;
1610
1611 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1612 if (status != PJ_SUCCESS)
1613 count = 0;
1614
1615 return count;
1616}
1617
1618
1619/*
1620 * Enumerate all conference ports.
1621 */
1622PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1623 unsigned *count)
1624{
1625 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1626}
1627
1628
1629/*
1630 * Get information about the specified conference port
1631 */
1632PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1633 pjsua_conf_port_info *info)
1634{
1635 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001636 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001637 pj_status_t status;
1638
1639 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1640 if (status != PJ_SUCCESS)
1641 return status;
1642
Benny Prijonoac623b32006-07-03 15:19:31 +00001643 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001644 info->slot_id = id;
1645 info->name = cinfo.name;
1646 info->clock_rate = cinfo.clock_rate;
1647 info->channel_count = cinfo.channel_count;
1648 info->samples_per_frame = cinfo.samples_per_frame;
1649 info->bits_per_sample = cinfo.bits_per_sample;
1650
1651 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001652 info->listener_cnt = cinfo.listener_cnt;
1653 for (i=0; i<cinfo.listener_cnt; ++i) {
1654 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001655 }
1656
1657 return PJ_SUCCESS;
1658}
1659
1660
1661/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001662 * Add arbitrary media port to PJSUA's conference bridge.
1663 */
1664PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1665 pjmedia_port *port,
1666 pjsua_conf_port_id *p_id)
1667{
1668 pj_status_t status;
1669
1670 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1671 port, NULL, (unsigned*)p_id);
1672 if (status != PJ_SUCCESS) {
1673 if (p_id)
1674 *p_id = PJSUA_INVALID_ID;
1675 }
1676
1677 return status;
1678}
1679
1680
1681/*
1682 * Remove arbitrary slot from the conference bridge.
1683 */
1684PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1685{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001686 pj_status_t status;
1687
1688 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1689 check_snd_dev_idle();
1690
1691 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00001692}
1693
1694
1695/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001696 * Establish unidirectional media flow from souce to sink.
1697 */
1698PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1699 pjsua_conf_port_id sink)
1700{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001701 /* If sound device idle timer is active, cancel it first. */
Benny Prijono0f711b42009-05-06 19:08:43 +00001702 PJSUA_LOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001703 if (pjsua_var.snd_idle_timer.id) {
1704 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
1705 pjsua_var.snd_idle_timer.id = PJ_FALSE;
1706 }
Benny Prijono0f711b42009-05-06 19:08:43 +00001707 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001708
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001709
Benny Prijonof798e502009-03-09 13:08:16 +00001710 /* For audio switchboard (i.e. APS-Direct):
1711 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001712 * (format, clock rate, channel count) must match to peer's.
1713 * Note that sound device can be reopened only if it doesn't have
1714 * any connection.
1715 */
Benny Prijonof798e502009-03-09 13:08:16 +00001716 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001717 pjmedia_conf_port_info port0_info;
1718 pjmedia_conf_port_info peer_info;
1719 unsigned peer_id;
1720 pj_bool_t need_reopen = PJ_FALSE;
1721 pj_status_t status;
1722
1723 peer_id = (source!=0)? source : sink;
1724 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
1725 &peer_info);
1726 pj_assert(status == PJ_SUCCESS);
1727
1728 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
1729 pj_assert(status == PJ_SUCCESS);
1730
1731 /* Check if sound device is instantiated. */
1732 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1733 !pjsua_var.no_snd);
1734
1735 /* Check if sound device need to reopen because it needs to modify
1736 * settings to match its peer. Sound device must be idle in this case
1737 * though.
1738 */
1739 if (!need_reopen &&
1740 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
1741 {
1742 need_reopen = (peer_info.format.id != port0_info.format.id ||
1743 peer_info.format.bitrate != port0_info.format.bitrate ||
1744 peer_info.clock_rate != port0_info.clock_rate ||
1745 peer_info.channel_count != port0_info.channel_count);
1746 }
1747
1748 if (need_reopen) {
Benny Prijonod65f78c2009-06-03 18:59:37 +00001749 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
1750 pjmedia_aud_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001751
Benny Prijonod65f78c2009-06-03 18:59:37 +00001752 /* Create parameter based on peer info */
1753 status = create_aud_param(&param, pjsua_var.cap_dev,
1754 pjsua_var.play_dev,
1755 peer_info.clock_rate,
1756 peer_info.channel_count,
1757 peer_info.samples_per_frame,
1758 peer_info.bits_per_sample);
1759 if (status != PJ_SUCCESS) {
1760 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1761 return status;
1762 }
Benny Prijonof798e502009-03-09 13:08:16 +00001763
Benny Prijonod65f78c2009-06-03 18:59:37 +00001764 /* And peer format */
1765 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
1766 param.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
1767 param.ext_fmt = peer_info.format;
1768 }
Benny Prijono10454dc2009-02-21 14:21:59 +00001769
Benny Prijonod65f78c2009-06-03 18:59:37 +00001770 status = open_snd_dev(&param);
1771 if (status != PJ_SUCCESS) {
1772 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1773 return status;
1774 }
1775 } else {
1776 /* Null-audio */
1777 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1778 if (status != PJ_SUCCESS) {
1779 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1780 return status;
1781 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001782 }
1783 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001784
Benny Prijonof798e502009-03-09 13:08:16 +00001785 } else {
1786 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001787
Benny Prijonof798e502009-03-09 13:08:16 +00001788 /* Create sound port if none is instantiated */
1789 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1790 !pjsua_var.no_snd)
1791 {
1792 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001793
Benny Prijonof798e502009-03-09 13:08:16 +00001794 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1795 if (status != PJ_SUCCESS) {
1796 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1797 return status;
1798 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001799 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001800
Benny Prijonof798e502009-03-09 13:08:16 +00001801 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001802
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001803 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
1804}
1805
1806
1807/*
1808 * Disconnect media flow from the source to destination port.
1809 */
1810PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
1811 pjsua_conf_port_id sink)
1812{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001813 pj_status_t status;
1814
1815 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001816 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001817
1818 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001819}
1820
1821
Benny Prijono6dd967c2006-12-26 02:27:14 +00001822/*
1823 * Adjust the signal level to be transmitted from the bridge to the
1824 * specified port by making it louder or quieter.
1825 */
1826PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
1827 float level)
1828{
1829 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
1830 (int)((level-1) * 128));
1831}
1832
1833/*
1834 * Adjust the signal level to be received from the specified port (to
1835 * the bridge) by making it louder or quieter.
1836 */
1837PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
1838 float level)
1839{
1840 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
1841 (int)((level-1) * 128));
1842}
1843
1844
1845/*
1846 * Get last signal level transmitted to or received from the specified port.
1847 */
1848PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
1849 unsigned *tx_level,
1850 unsigned *rx_level)
1851{
1852 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
1853 tx_level, rx_level);
1854}
1855
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001856/*****************************************************************************
1857 * File player.
1858 */
1859
Benny Prijonod5696da2007-07-17 16:25:45 +00001860static char* get_basename(const char *path, unsigned len)
1861{
1862 char *p = ((char*)path) + len;
1863
1864 if (len==0)
1865 return p;
1866
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001867 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00001868
1869 return (p==path) ? p : p+1;
1870}
1871
1872
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001873/*
1874 * Create a file player, and automatically connect this player to
1875 * the conference bridge.
1876 */
1877PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1878 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001879 pjsua_player_id *p_id)
1880{
1881 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001882 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00001883 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001884 pjmedia_port *port;
1885 pj_status_t status;
1886
1887 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1888 return PJ_ETOOMANY;
1889
1890 PJSUA_LOCK();
1891
1892 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1893 if (pjsua_var.player[file_id].port == NULL)
1894 break;
1895 }
1896
1897 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1898 /* This is unexpected */
1899 PJSUA_UNLOCK();
1900 pj_assert(0);
1901 return PJ_EBUG;
1902 }
1903
1904 pj_memcpy(path, filename->ptr, filename->slen);
1905 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00001906
1907 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1908 if (!pool) {
1909 PJSUA_UNLOCK();
1910 return PJ_ENOMEM;
1911 }
1912
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00001913 status = pjmedia_wav_player_port_create(
1914 pool, path,
1915 pjsua_var.mconf_cfg.samples_per_frame *
1916 1000 / pjsua_var.media_cfg.channel_count /
1917 pjsua_var.media_cfg.clock_rate,
1918 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001919 if (status != PJ_SUCCESS) {
1920 PJSUA_UNLOCK();
1921 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001922 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001923 return status;
1924 }
1925
Benny Prijono5297af92008-03-18 13:40:40 +00001926 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001927 port, filename, &slot);
1928 if (status != PJ_SUCCESS) {
1929 pjmedia_port_destroy(port);
1930 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00001931 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1932 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001933 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001934 return status;
1935 }
1936
Benny Prijonoa66c3312007-01-21 23:12:40 +00001937 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00001938 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001939 pjsua_var.player[file_id].port = port;
1940 pjsua_var.player[file_id].slot = slot;
1941
1942 if (p_id) *p_id = file_id;
1943
1944 ++pjsua_var.player_cnt;
1945
1946 PJSUA_UNLOCK();
1947 return PJ_SUCCESS;
1948}
1949
1950
1951/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00001952 * Create a file playlist media port, and automatically add the port
1953 * to the conference bridge.
1954 */
1955PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
1956 unsigned file_count,
1957 const pj_str_t *label,
1958 unsigned options,
1959 pjsua_player_id *p_id)
1960{
1961 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00001962 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001963 pjmedia_port *port;
1964 pj_status_t status;
1965
1966 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1967 return PJ_ETOOMANY;
1968
1969 PJSUA_LOCK();
1970
1971 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1972 if (pjsua_var.player[file_id].port == NULL)
1973 break;
1974 }
1975
1976 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1977 /* This is unexpected */
1978 PJSUA_UNLOCK();
1979 pj_assert(0);
1980 return PJ_EBUG;
1981 }
1982
1983
1984 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
1985 pjsua_var.media_cfg.clock_rate;
1986
Benny Prijonod5696da2007-07-17 16:25:45 +00001987 pool = pjsua_pool_create("playlist", 1000, 1000);
1988 if (!pool) {
1989 PJSUA_UNLOCK();
1990 return PJ_ENOMEM;
1991 }
1992
1993 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001994 file_names, file_count,
1995 ptime, options, 0, &port);
1996 if (status != PJ_SUCCESS) {
1997 PJSUA_UNLOCK();
1998 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001999 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002000 return status;
2001 }
2002
Benny Prijonod5696da2007-07-17 16:25:45 +00002003 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002004 port, &port->info.name, &slot);
2005 if (status != PJ_SUCCESS) {
2006 pjmedia_port_destroy(port);
2007 PJSUA_UNLOCK();
2008 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002009 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002010 return status;
2011 }
2012
2013 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00002014 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002015 pjsua_var.player[file_id].port = port;
2016 pjsua_var.player[file_id].slot = slot;
2017
2018 if (p_id) *p_id = file_id;
2019
2020 ++pjsua_var.player_cnt;
2021
2022 PJSUA_UNLOCK();
2023 return PJ_SUCCESS;
2024
2025}
2026
2027
2028/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002029 * Get conference port ID associated with player.
2030 */
2031PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
2032{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002033 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002034 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2035
2036 return pjsua_var.player[id].slot;
2037}
2038
Benny Prijono469b1522006-12-26 03:05:17 +00002039/*
2040 * Get the media port for the player.
2041 */
Benny Prijonobe41d862008-01-18 13:24:28 +00002042PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00002043 pjmedia_port **p_port)
2044{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002045 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002046 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2047 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2048
2049 *p_port = pjsua_var.player[id].port;
2050
2051 return PJ_SUCCESS;
2052}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002053
2054/*
2055 * Set playback position.
2056 */
2057PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
2058 pj_uint32_t samples)
2059{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002060 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002061 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002062 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002063
2064 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
2065}
2066
2067
2068/*
2069 * Close the file, remove the player from the bridge, and free
2070 * resources associated with the file player.
2071 */
2072PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
2073{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002074 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002075 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2076
2077 PJSUA_LOCK();
2078
2079 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002080 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002081 pjmedia_port_destroy(pjsua_var.player[id].port);
2082 pjsua_var.player[id].port = NULL;
2083 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002084 pj_pool_release(pjsua_var.player[id].pool);
2085 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002086 pjsua_var.player_cnt--;
2087 }
2088
2089 PJSUA_UNLOCK();
2090
2091 return PJ_SUCCESS;
2092}
2093
2094
2095/*****************************************************************************
2096 * File recorder.
2097 */
2098
2099/*
2100 * Create a file recorder, and automatically connect this recorder to
2101 * the conference bridge.
2102 */
2103PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00002104 unsigned enc_type,
2105 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002106 pj_ssize_t max_size,
2107 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002108 pjsua_recorder_id *p_id)
2109{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002110 enum Format
2111 {
2112 FMT_UNKNOWN,
2113 FMT_WAV,
2114 FMT_MP3,
2115 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002116 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002117 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002118 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00002119 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00002120 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002121 pjmedia_port *port;
2122 pj_status_t status;
2123
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002124 /* Filename must present */
2125 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
2126
Benny Prijono00cae612006-07-31 15:19:36 +00002127 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002128 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002129
Benny Prijono8f310522006-10-20 11:08:49 +00002130 /* Don't support encoding type at present */
2131 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002132
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002133 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
2134 return PJ_ETOOMANY;
2135
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002136 /* Determine the file format */
2137 ext.ptr = filename->ptr + filename->slen - 4;
2138 ext.slen = 4;
2139
2140 if (pj_stricmp2(&ext, ".wav") == 0)
2141 file_format = FMT_WAV;
2142 else if (pj_stricmp2(&ext, ".mp3") == 0)
2143 file_format = FMT_MP3;
2144 else {
2145 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
2146 "determine file format for %.*s",
2147 (int)filename->slen, filename->ptr));
2148 return PJ_ENOTSUP;
2149 }
2150
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002151 PJSUA_LOCK();
2152
2153 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
2154 if (pjsua_var.recorder[file_id].port == NULL)
2155 break;
2156 }
2157
2158 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
2159 /* This is unexpected */
2160 PJSUA_UNLOCK();
2161 pj_assert(0);
2162 return PJ_EBUG;
2163 }
2164
2165 pj_memcpy(path, filename->ptr, filename->slen);
2166 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002167
Benny Prijonod5696da2007-07-17 16:25:45 +00002168 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2169 if (!pool) {
2170 PJSUA_UNLOCK();
2171 return PJ_ENOMEM;
2172 }
2173
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002174 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00002175 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002176 pjsua_var.media_cfg.clock_rate,
2177 pjsua_var.mconf_cfg.channel_count,
2178 pjsua_var.mconf_cfg.samples_per_frame,
2179 pjsua_var.mconf_cfg.bits_per_sample,
2180 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002181 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00002182 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002183 port = NULL;
2184 status = PJ_ENOTSUP;
2185 }
2186
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002187 if (status != PJ_SUCCESS) {
2188 PJSUA_UNLOCK();
2189 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002190 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002191 return status;
2192 }
2193
Benny Prijonod5696da2007-07-17 16:25:45 +00002194 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002195 port, filename, &slot);
2196 if (status != PJ_SUCCESS) {
2197 pjmedia_port_destroy(port);
2198 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00002199 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002200 return status;
2201 }
2202
2203 pjsua_var.recorder[file_id].port = port;
2204 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00002205 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002206
2207 if (p_id) *p_id = file_id;
2208
2209 ++pjsua_var.rec_cnt;
2210
2211 PJSUA_UNLOCK();
2212 return PJ_SUCCESS;
2213}
2214
2215
2216/*
2217 * Get conference port associated with recorder.
2218 */
2219PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
2220{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002221 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2222 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002223 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2224
2225 return pjsua_var.recorder[id].slot;
2226}
2227
Benny Prijono469b1522006-12-26 03:05:17 +00002228/*
2229 * Get the media port for the recorder.
2230 */
2231PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
2232 pjmedia_port **p_port)
2233{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002234 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2235 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002236 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2237 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2238
2239 *p_port = pjsua_var.recorder[id].port;
2240 return PJ_SUCCESS;
2241}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002242
2243/*
2244 * Destroy recorder (this will complete recording).
2245 */
2246PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
2247{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002248 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2249 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002250 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2251
2252 PJSUA_LOCK();
2253
2254 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002255 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002256 pjmedia_port_destroy(pjsua_var.recorder[id].port);
2257 pjsua_var.recorder[id].port = NULL;
2258 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002259 pj_pool_release(pjsua_var.recorder[id].pool);
2260 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002261 pjsua_var.rec_cnt--;
2262 }
2263
2264 PJSUA_UNLOCK();
2265
2266 return PJ_SUCCESS;
2267}
2268
2269
2270/*****************************************************************************
2271 * Sound devices.
2272 */
2273
2274/*
2275 * Enum sound devices.
2276 */
Benny Prijonof798e502009-03-09 13:08:16 +00002277
2278PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002279 unsigned *count)
2280{
2281 unsigned i, dev_count;
2282
Benny Prijono10454dc2009-02-21 14:21:59 +00002283 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002284
2285 if (dev_count > *count) dev_count = *count;
2286
2287 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00002288 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002289
Benny Prijono10454dc2009-02-21 14:21:59 +00002290 status = pjmedia_aud_dev_get_info(i, &info[i]);
2291 if (status != PJ_SUCCESS)
2292 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002293 }
2294
2295 *count = dev_count;
2296
2297 return PJ_SUCCESS;
2298}
Benny Prijonof798e502009-03-09 13:08:16 +00002299
2300
Benny Prijono10454dc2009-02-21 14:21:59 +00002301PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
2302 unsigned *count)
2303{
2304 unsigned i, dev_count;
2305
2306 dev_count = pjmedia_aud_dev_count();
2307
2308 if (dev_count > *count) dev_count = *count;
2309 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
2310
2311 for (i=0; i<dev_count; ++i) {
2312 pjmedia_aud_dev_info ai;
2313 pj_status_t status;
2314
2315 status = pjmedia_aud_dev_get_info(i, &ai);
2316 if (status != PJ_SUCCESS)
2317 return status;
2318
2319 strncpy(info[i].name, ai.name, sizeof(info[i].name));
2320 info[i].name[sizeof(info[i].name)-1] = '\0';
2321 info[i].input_count = ai.input_count;
2322 info[i].output_count = ai.output_count;
2323 info[i].default_samples_per_sec = ai.default_samples_per_sec;
2324 }
2325
2326 *count = dev_count;
2327
2328 return PJ_SUCCESS;
2329}
Benny Prijono10454dc2009-02-21 14:21:59 +00002330
Benny Prijonof798e502009-03-09 13:08:16 +00002331/* Create audio device parameter to open the device */
2332static pj_status_t create_aud_param(pjmedia_aud_param *param,
2333 pjmedia_aud_dev_index capture_dev,
2334 pjmedia_aud_dev_index playback_dev,
2335 unsigned clock_rate,
2336 unsigned channel_count,
2337 unsigned samples_per_frame,
2338 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002339{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002340 pj_status_t status;
2341
Benny Prijono96e74f32009-02-22 12:00:12 +00002342 /* Normalize device ID with new convention about default device ID */
2343 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
2344 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
2345
Benny Prijono10454dc2009-02-21 14:21:59 +00002346 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00002347 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00002348 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00002349 pjsua_perror(THIS_FILE, "Error retrieving default audio "
2350 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00002351 return status;
2352 }
Benny Prijonof798e502009-03-09 13:08:16 +00002353 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
2354 param->rec_id = capture_dev;
2355 param->play_id = playback_dev;
2356 param->clock_rate = clock_rate;
2357 param->channel_count = channel_count;
2358 param->samples_per_frame = samples_per_frame;
2359 param->bits_per_sample = bits_per_sample;
2360
2361 /* Update the setting with user preference */
2362#define update_param(cap, field) \
2363 if (pjsua_var.aud_param.flags & cap) { \
2364 param->flags |= cap; \
2365 param->field = pjsua_var.aud_param.field; \
2366 }
2367 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
2368 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
2369 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
2370 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
2371#undef update_param
2372
Benny Prijono10454dc2009-02-21 14:21:59 +00002373 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00002374 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
2375 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
2376 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
2377 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
2378
Benny Prijono10454dc2009-02-21 14:21:59 +00002379 /* EC settings */
2380 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00002381 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
2382 param->ec_enabled = PJ_TRUE;
2383 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00002384 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00002385 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00002386 }
2387
Benny Prijonof798e502009-03-09 13:08:16 +00002388 return PJ_SUCCESS;
2389}
Benny Prijono26056d82006-10-11 16:03:41 +00002390
Benny Prijonof798e502009-03-09 13:08:16 +00002391/* Internal: the first time the audio device is opened (during app
2392 * startup), retrieve the audio settings such as volume level
2393 * so that aud_get_settings() will work.
2394 */
2395static pj_status_t update_initial_aud_param()
2396{
2397 pjmedia_aud_stream *strm;
2398 pjmedia_aud_param param;
2399 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00002400
Benny Prijonof798e502009-03-09 13:08:16 +00002401 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002402
Benny Prijonof798e502009-03-09 13:08:16 +00002403 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00002404
Benny Prijonof798e502009-03-09 13:08:16 +00002405 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002406 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00002407 pjsua_perror(THIS_FILE, "Error audio stream "
2408 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002409 return status;
2410 }
2411
Benny Prijonof798e502009-03-09 13:08:16 +00002412#define update_saved_param(cap, field) \
2413 if (param.flags & cap) { \
2414 pjsua_var.aud_param.flags |= cap; \
2415 pjsua_var.aud_param.field = param.field; \
2416 }
2417
2418 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
2419 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
2420 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
2421 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
2422#undef update_saved_param
2423
2424 return PJ_SUCCESS;
2425}
2426
2427/* Get format name */
2428static const char *get_fmt_name(pj_uint32_t id)
2429{
2430 static char name[8];
2431
2432 if (id == PJMEDIA_FORMAT_L16)
2433 return "PCM";
2434 pj_memcpy(name, &id, 4);
2435 name[4] = '\0';
2436 return name;
2437}
2438
2439/* Open sound device with the setting. */
2440static pj_status_t open_snd_dev(pjmedia_aud_param *param)
2441{
2442 pjmedia_port *conf_port;
2443 pj_status_t status;
2444
2445 PJ_ASSERT_RETURN(param, PJ_EINVAL);
2446
2447 /* Check if NULL sound device is used */
2448 if (NULL_SND_DEV_ID==param->rec_id || NULL_SND_DEV_ID==param->play_id) {
2449 return pjsua_set_null_snd_dev();
2450 }
2451
2452 /* Close existing sound port */
2453 close_snd_dev();
2454
2455 /* Create memory pool for sound device. */
2456 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2457 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2458
2459
2460 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
2461 get_fmt_name(param->ext_fmt.id),
2462 param->clock_rate, param->channel_count,
2463 param->samples_per_frame / param->channel_count * 1000 /
2464 param->clock_rate));
2465
2466 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
2467 param, &pjsua_var.snd_port);
2468 if (status != PJ_SUCCESS)
2469 return status;
2470
2471 /* Get the port0 of the conference bridge. */
2472 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2473 pj_assert(conf_port != NULL);
2474
2475 /* For conference bridge, resample if necessary if the bridge's
2476 * clock rate is different than the sound device's clock rate.
2477 */
2478 if (!pjsua_var.is_mswitch &&
2479 param->ext_fmt.id == PJMEDIA_FORMAT_PCM &&
2480 conf_port->info.clock_rate != param->clock_rate)
2481 {
2482 pjmedia_port *resample_port;
2483 unsigned resample_opt = 0;
2484
2485 if (pjsua_var.media_cfg.quality >= 3 &&
2486 pjsua_var.media_cfg.quality <= 4)
2487 {
2488 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
2489 }
2490 else if (pjsua_var.media_cfg.quality < 3) {
2491 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
2492 }
2493
2494 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
2495 conf_port,
2496 param->clock_rate,
2497 resample_opt,
2498 &resample_port);
2499 if (status != PJ_SUCCESS) {
2500 char errmsg[PJ_ERR_MSG_SIZE];
2501 pj_strerror(status, errmsg, sizeof(errmsg));
2502 PJ_LOG(4, (THIS_FILE,
2503 "Error creating resample port: %s",
2504 errmsg));
2505 close_snd_dev();
2506 return status;
2507 }
2508
2509 conf_port = resample_port;
2510 }
2511
2512 /* Otherwise for audio switchboard, the switch's port0 setting is
2513 * derived from the sound device setting, so update the setting.
2514 */
2515 if (pjsua_var.is_mswitch) {
2516 pj_memcpy(&conf_port->info.format, &param->ext_fmt,
2517 sizeof(conf_port->info.format));
2518 conf_port->info.clock_rate = param->clock_rate;
2519 conf_port->info.samples_per_frame = param->samples_per_frame;
2520 conf_port->info.channel_count = param->channel_count;
2521 conf_port->info.bits_per_sample = 16;
2522 }
2523
2524 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00002525 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
2526 conf_port );
2527 if (status != PJ_SUCCESS) {
2528 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
2529 "sound device", status);
2530 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2531 pjsua_var.snd_port = NULL;
2532 return status;
2533 }
2534
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002535 /* Save the device IDs */
Benny Prijonof798e502009-03-09 13:08:16 +00002536 pjsua_var.cap_dev = param->rec_id;
2537 pjsua_var.play_dev = param->play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002538
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002539 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00002540 {
2541 pjmedia_aud_dev_info rec_info;
2542 pjmedia_aud_stream *strm;
2543 pjmedia_aud_param si;
2544 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002545
Benny Prijonof798e502009-03-09 13:08:16 +00002546 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2547 status = pjmedia_aud_stream_get_param(strm, &si);
2548 if (status == PJ_SUCCESS)
2549 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00002550
Benny Prijonof798e502009-03-09 13:08:16 +00002551 if (status==PJ_SUCCESS) {
2552 if (param->clock_rate != pjsua_var.media_cfg.clock_rate) {
2553 char tmp_buf[128];
2554 int tmp_buf_len = sizeof(tmp_buf);
2555
2556 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
2557 "%s (%dKHz)",
2558 rec_info.name,
2559 param->clock_rate/1000);
2560 pj_strset(&tmp, tmp_buf, tmp_buf_len);
2561 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
2562 } else {
2563 pjmedia_conf_set_port0_name(pjsua_var.mconf,
2564 pj_cstr(&tmp, rec_info.name));
2565 }
2566 }
2567
2568 /* Any error is not major, let it through */
2569 status = PJ_SUCCESS;
2570 };
2571
2572 /* If this is the first time the audio device is open, retrieve some
2573 * settings from the device (such as volume settings) so that the
2574 * pjsua_snd_get_setting() work.
2575 */
2576 if (pjsua_var.aud_open_cnt == 0) {
2577 update_initial_aud_param();
2578 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00002579 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002580
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002581 return PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00002582}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002583
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002584
Benny Prijonof798e502009-03-09 13:08:16 +00002585/* Close existing sound device */
2586static void close_snd_dev(void)
2587{
2588 /* Close sound device */
2589 if (pjsua_var.snd_port) {
2590 pjmedia_aud_dev_info cap_info, play_info;
2591 pjmedia_aud_stream *strm;
2592 pjmedia_aud_param param;
2593
2594 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2595 pjmedia_aud_stream_get_param(strm, &param);
2596
2597 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
2598 cap_info.name[0] = '\0';
2599 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
2600 play_info.name[0] = '\0';
2601
2602 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
2603 "%s sound capture device",
2604 play_info.name, cap_info.name));
2605
2606 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
2607 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2608 pjsua_var.snd_port = NULL;
2609 }
2610
2611 /* Close null sound device */
2612 if (pjsua_var.null_snd) {
2613 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
2614 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
2615 pjsua_var.null_snd = NULL;
2616 }
2617
2618 if (pjsua_var.snd_pool)
2619 pj_pool_release(pjsua_var.snd_pool);
2620 pjsua_var.snd_pool = NULL;
2621}
2622
2623
2624/*
2625 * Select or change sound device. Application may call this function at
2626 * any time to replace current sound device.
2627 */
2628PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
2629 int playback_dev)
2630{
2631 unsigned alt_cr_cnt = 1;
2632 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
2633 unsigned i;
2634 pj_status_t status = -1;
2635
Benny Prijono23ea21a2009-06-03 12:43:06 +00002636 /* Null-sound */
2637 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
2638 return pjsua_set_null_snd_dev();
2639 }
2640
Benny Prijonof798e502009-03-09 13:08:16 +00002641 /* Set default clock rate */
2642 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
2643 if (alt_cr[0] == 0)
2644 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
2645
2646 /* Allow retrying of different clock rate if we're using conference
2647 * bridge (meaning audio format is always PCM), otherwise lock on
2648 * to one clock rate.
2649 */
2650 if (pjsua_var.is_mswitch) {
2651 alt_cr_cnt = 1;
2652 } else {
2653 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
2654 }
2655
2656 /* Attempts to open the sound device with different clock rates */
2657 for (i=0; i<alt_cr_cnt; ++i) {
2658 pjmedia_aud_param param;
2659 unsigned samples_per_frame;
2660
2661 /* Create the default audio param */
2662 samples_per_frame = alt_cr[i] *
2663 pjsua_var.media_cfg.audio_frame_ptime *
2664 pjsua_var.media_cfg.channel_count / 1000;
2665 status = create_aud_param(&param, capture_dev, playback_dev,
2666 alt_cr[i], pjsua_var.media_cfg.channel_count,
2667 samples_per_frame, 16);
2668 if (status != PJ_SUCCESS)
2669 return status;
2670
2671 /* Open! */
2672 status = open_snd_dev(&param);
2673 if (status == PJ_SUCCESS)
2674 break;
2675 }
2676
2677 if (status != PJ_SUCCESS) {
2678 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
2679 return status;
2680 }
2681
2682 return PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002683}
2684
2685
2686/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00002687 * Get currently active sound devices. If sound devices has not been created
2688 * (for example when pjsua_start() is not called), it is possible that
2689 * the function returns PJ_SUCCESS with -1 as device IDs.
2690 */
2691PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
2692 int *playback_dev)
2693{
2694 if (capture_dev) {
2695 *capture_dev = pjsua_var.cap_dev;
2696 }
2697 if (playback_dev) {
2698 *playback_dev = pjsua_var.play_dev;
2699 }
2700
2701 return PJ_SUCCESS;
2702}
2703
2704
2705/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002706 * Use null sound device.
2707 */
2708PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
2709{
2710 pjmedia_port *conf_port;
2711 pj_status_t status;
2712
2713 /* Close existing sound device */
2714 close_snd_dev();
2715
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002716 /* Create memory pool for sound device. */
2717 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2718 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2719
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002720 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
2721
2722 /* Get the port0 of the conference bridge. */
2723 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2724 pj_assert(conf_port != NULL);
2725
2726 /* Create master port, connecting port0 of the conference bridge to
2727 * a null port.
2728 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002729 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002730 conf_port, 0, &pjsua_var.null_snd);
2731 if (status != PJ_SUCCESS) {
2732 pjsua_perror(THIS_FILE, "Unable to create null sound device",
2733 status);
2734 return status;
2735 }
2736
2737 /* Start the master port */
2738 status = pjmedia_master_port_start(pjsua_var.null_snd);
2739 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2740
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002741 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2742 pjsua_var.play_dev = NULL_SND_DEV_ID;
2743
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002744 return PJ_SUCCESS;
2745}
2746
2747
Benny Prijonoe909eac2006-07-27 22:04:56 +00002748
2749/*
2750 * Use no device!
2751 */
2752PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2753{
2754 /* Close existing sound device */
2755 close_snd_dev();
2756
2757 pjsua_var.no_snd = PJ_TRUE;
2758 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2759}
2760
2761
Benny Prijonof20687a2006-08-04 18:27:19 +00002762/*
2763 * Configure the AEC settings of the sound port.
2764 */
Benny Prijono5da50432006-08-07 10:24:52 +00002765PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00002766{
2767 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2768
2769 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00002770 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
2771 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00002772
2773 return PJ_SUCCESS;
2774}
2775
2776
2777/*
2778 * Get current AEC tail length.
2779 */
Benny Prijono22dfe592006-08-06 12:07:13 +00002780PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00002781{
2782 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2783 return PJ_SUCCESS;
2784}
2785
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002786
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002787/*
Benny Prijonof798e502009-03-09 13:08:16 +00002788 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002789 */
Benny Prijonof798e502009-03-09 13:08:16 +00002790PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002791{
Benny Prijonof798e502009-03-09 13:08:16 +00002792 return pjsua_var.snd_port != NULL;
2793}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002794
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002795
Benny Prijonof798e502009-03-09 13:08:16 +00002796/*
2797 * Configure sound device setting to the sound device being used.
2798 */
2799PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
2800 const void *pval,
2801 pj_bool_t keep)
2802{
Benny Prijono09b0ff62009-03-10 12:07:51 +00002803 pj_status_t status;
2804
Benny Prijonof798e502009-03-09 13:08:16 +00002805 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00002806 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00002807 return PJMEDIA_EAUD_INVCAP;
2808 }
2809
Benny Prijono09b0ff62009-03-10 12:07:51 +00002810 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00002811 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00002812 pjmedia_aud_stream *strm;
2813
2814 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00002815 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00002816 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00002817 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00002818 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00002819
2820 if (status != PJ_SUCCESS)
2821 return status;
2822
2823 /* Save in internal param for later device open */
2824 if (keep) {
2825 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
2826 cap, pval);
2827 }
2828
2829 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00002830}
2831
2832/*
2833 * Retrieve a sound device setting.
2834 */
2835PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
2836 void *pval)
2837{
2838 /* If sound device has never been opened before, open it to
2839 * retrieve the initial setting from the device (e.g. audio
2840 * volume)
2841 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00002842 if (pjsua_var.aud_open_cnt==0) {
2843 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00002844 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00002845 close_snd_dev();
2846 }
Benny Prijonof798e502009-03-09 13:08:16 +00002847
2848 if (pjsua_snd_is_active()) {
2849 /* Sound is active, retrieve from device directly */
2850 pjmedia_aud_stream *strm;
2851
2852 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2853 return pjmedia_aud_stream_get_cap(strm, cap, pval);
2854 } else {
2855 /* Otherwise retrieve from internal param */
2856 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
2857 cap, pval);
2858 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002859}
2860
2861
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002862/*****************************************************************************
2863 * Codecs.
2864 */
2865
2866/*
2867 * Enum all supported codecs in the system.
2868 */
2869PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
2870 unsigned *p_count )
2871{
2872 pjmedia_codec_mgr *codec_mgr;
2873 pjmedia_codec_info info[32];
2874 unsigned i, count, prio[32];
2875 pj_status_t status;
2876
2877 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2878 count = PJ_ARRAY_SIZE(info);
2879 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
2880 if (status != PJ_SUCCESS) {
2881 *p_count = 0;
2882 return status;
2883 }
2884
2885 if (count > *p_count) count = *p_count;
2886
2887 for (i=0; i<count; ++i) {
2888 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
2889 id[i].codec_id = pj_str(id[i].buf_);
2890 id[i].priority = (pj_uint8_t) prio[i];
2891 }
2892
2893 *p_count = count;
2894
2895 return PJ_SUCCESS;
2896}
2897
2898
2899/*
2900 * Change codec priority.
2901 */
2902PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
2903 pj_uint8_t priority )
2904{
Benny Prijono88accae2008-06-26 15:48:14 +00002905 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002906 pjmedia_codec_mgr *codec_mgr;
2907
2908 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2909
Benny Prijono88accae2008-06-26 15:48:14 +00002910 if (codec_id->slen==1 && *codec_id->ptr=='*')
2911 codec_id = &all;
2912
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002913 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
2914 priority);
2915}
2916
2917
2918/*
2919 * Get codec parameters.
2920 */
2921PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
2922 pjmedia_codec_param *param )
2923{
Benny Prijono88accae2008-06-26 15:48:14 +00002924 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002925 const pjmedia_codec_info *info;
2926 pjmedia_codec_mgr *codec_mgr;
2927 unsigned count = 1;
2928 pj_status_t status;
2929
2930 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2931
Benny Prijono88accae2008-06-26 15:48:14 +00002932 if (codec_id->slen==1 && *codec_id->ptr=='*')
2933 codec_id = &all;
2934
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002935 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
2936 &count, &info, NULL);
2937 if (status != PJ_SUCCESS)
2938 return status;
2939
2940 if (count != 1)
2941 return PJ_ENOTFOUND;
2942
2943 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
2944 return status;
2945}
2946
2947
2948/*
2949 * Set codec parameters.
2950 */
2951PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
2952 const pjmedia_codec_param *param)
2953{
Benny Prijono00cae612006-07-31 15:19:36 +00002954 PJ_UNUSED_ARG(id);
2955 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002956 PJ_TODO(set_codec_param);
2957 return PJ_SUCCESS;
2958}