blob: 958756f98f2f77247f32d6801dc776af8945e468 [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{
498
499 /* Activate sound device auto-close timer if sound device is idle.
500 * It is idle when there is no port connection in the bridge.
501 */
502 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) &&
503 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
504 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
505 pjsua_var.media_cfg.snd_auto_close_time >= 0)
506 {
507 pj_time_val delay;
508
509 delay.msec = 0;
510 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
511
512 pjsua_var.snd_idle_timer.id = PJ_TRUE;
513 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
514 &delay);
515 }
516}
517
518
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000519/* Timer callback to close sound device */
520static void close_snd_timer_cb( pj_timer_heap_t *th,
521 pj_timer_entry *entry)
522{
523 PJ_UNUSED_ARG(th);
524
Benny Prijono0f711b42009-05-06 19:08:43 +0000525 PJSUA_LOCK();
526 if (entry->id) {
527 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
528 pjsua_var.media_cfg.snd_auto_close_time));
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000529
Benny Prijono0f711b42009-05-06 19:08:43 +0000530 entry->id = PJ_FALSE;
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000531
Benny Prijono0f711b42009-05-06 19:08:43 +0000532 close_snd_dev();
533 }
534 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000535}
536
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000537
538/*
539 * Start pjsua media subsystem.
540 */
541pj_status_t pjsua_media_subsys_start(void)
542{
543 pj_status_t status;
544
545 /* Create media for calls, if none is specified */
546 if (pjsua_var.calls[0].med_tp == NULL) {
547 pjsua_transport_config transport_cfg;
548
549 /* Create default transport config */
550 pjsua_transport_config_default(&transport_cfg);
551 transport_cfg.port = DEFAULT_RTP_PORT;
552
553 status = pjsua_media_transports_create(&transport_cfg);
554 if (status != PJ_SUCCESS)
555 return status;
556 }
557
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000558 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
559 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000560
561 return PJ_SUCCESS;
562}
563
564
565/*
566 * Destroy pjsua media subsystem.
567 */
568pj_status_t pjsua_media_subsys_destroy(void)
569{
570 unsigned i;
571
572 close_snd_dev();
573
574 if (pjsua_var.mconf) {
575 pjmedia_conf_destroy(pjsua_var.mconf);
576 pjsua_var.mconf = NULL;
577 }
578
579 if (pjsua_var.null_port) {
580 pjmedia_port_destroy(pjsua_var.null_port);
581 pjsua_var.null_port = NULL;
582 }
583
584 /* Destroy file players */
585 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
586 if (pjsua_var.player[i].port) {
587 pjmedia_port_destroy(pjsua_var.player[i].port);
588 pjsua_var.player[i].port = NULL;
589 }
590 }
591
592 /* Destroy file recorders */
593 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
594 if (pjsua_var.recorder[i].port) {
595 pjmedia_port_destroy(pjsua_var.recorder[i].port);
596 pjsua_var.recorder[i].port = NULL;
597 }
598 }
599
600 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000601 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono311b63f2008-07-14 11:31:40 +0000602 if (pjsua_var.calls[i].med_tp_st != PJSUA_MED_TP_IDLE) {
603 pjsua_media_channel_deinit(i);
604 }
Benny Prijono40860c32008-09-04 13:55:33 +0000605 if (pjsua_var.calls[i].med_tp && pjsua_var.calls[i].med_tp_auto_del) {
606 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000607 }
Benny Prijono40860c32008-09-04 13:55:33 +0000608 pjsua_var.calls[i].med_tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000609 }
610
611 /* Destroy media endpoint. */
612 if (pjsua_var.med_endpt) {
613
614 /* Shutdown all codecs: */
615# if PJMEDIA_HAS_SPEEX_CODEC
616 pjmedia_codec_speex_deinit();
617# endif /* PJMEDIA_HAS_SPEEX_CODEC */
618
619# if PJMEDIA_HAS_GSM_CODEC
620 pjmedia_codec_gsm_deinit();
621# endif /* PJMEDIA_HAS_GSM_CODEC */
622
623# if PJMEDIA_HAS_G711_CODEC
624 pjmedia_codec_g711_deinit();
625# endif /* PJMEDIA_HAS_G711_CODEC */
626
Benny Prijono7ffd7752008-03-17 14:07:53 +0000627# if PJMEDIA_HAS_G722_CODEC
628 pjmedia_codec_g722_deinit();
629# endif /* PJMEDIA_HAS_G722_CODEC */
630
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000631# if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000632 pjmedia_codec_ipp_deinit();
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000633# endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000634
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000635# if PJMEDIA_HAS_PASSTHROUGH_CODECS
636 pjmedia_codec_passthrough_deinit();
637# endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
638
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000639# if PJMEDIA_HAS_G7221_CODEC
640 pjmedia_codec_g7221_deinit();
641# endif /* PJMEDIA_HAS_G7221_CODEC */
642
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000643# if PJMEDIA_HAS_L16_CODEC
644 pjmedia_codec_l16_deinit();
645# endif /* PJMEDIA_HAS_L16_CODEC */
646
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000647 pjmedia_endpt_destroy(pjsua_var.med_endpt);
648 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000649
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000650 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000651 // Not necessary, as pjmedia_snd_deinit() should have been called
652 // in pjmedia_endpt_destroy().
653 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000654 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000655
Benny Prijonode479562007-03-15 10:23:55 +0000656 /* Reset RTP port */
657 next_rtp_port = 0;
658
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000659 return PJ_SUCCESS;
660}
661
662
Benny Prijonoc97608e2007-03-23 16:34:20 +0000663/* Create normal UDP media transports */
664static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000665{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000666 unsigned i;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000667 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000668 pj_status_t status;
669
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000670 /* Create each media transport */
671 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
672
Benny Prijono617c5bc2007-04-02 19:51:21 +0000673 status = create_rtp_rtcp_sock(cfg, &skinfo);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000674 if (status != PJ_SUCCESS) {
675 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
676 status);
677 goto on_error;
678 }
Benny Prijonod8179652008-01-23 20:39:07 +0000679
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000680 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000681 &skinfo, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000682 &pjsua_var.calls[i].med_tp);
683 if (status != PJ_SUCCESS) {
684 pjsua_perror(THIS_FILE, "Unable to create media transport",
685 status);
686 goto on_error;
687 }
Benny Prijono00cae612006-07-31 15:19:36 +0000688
Benny Prijonod8179652008-01-23 20:39:07 +0000689 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
690 PJMEDIA_DIR_ENCODING,
691 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000692
Benny Prijonod8179652008-01-23 20:39:07 +0000693 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
694 PJMEDIA_DIR_DECODING,
695 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000696
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000697 }
698
Benny Prijonoc97608e2007-03-23 16:34:20 +0000699 return PJ_SUCCESS;
700
701on_error:
702 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
703 if (pjsua_var.calls[i].med_tp != NULL) {
704 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
705 pjsua_var.calls[i].med_tp = NULL;
706 }
707 }
708
709 return status;
710}
711
712
Benny Prijono096c56c2007-09-15 08:30:16 +0000713/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000714static void on_ice_complete(pjmedia_transport *tp,
715 pj_ice_strans_op op,
716 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000717{
Benny Prijonof76e1392008-06-06 14:51:48 +0000718 unsigned id;
Benny Prijono096c56c2007-09-15 08:30:16 +0000719 pj_bool_t found = PJ_FALSE;
720
Benny Prijono096c56c2007-09-15 08:30:16 +0000721 /* Find call which has this media transport */
722
723 PJSUA_LOCK();
724
Benny Prijonof76e1392008-06-06 14:51:48 +0000725 for (id=0; id<pjsua_var.ua_cfg.max_calls; ++id) {
726 if (pjsua_var.calls[id].med_tp == tp ||
727 pjsua_var.calls[id].med_orig == tp)
728 {
729 found = PJ_TRUE;
730 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000731 }
732 }
733
734 PJSUA_UNLOCK();
735
Benny Prijonof76e1392008-06-06 14:51:48 +0000736 if (!found)
737 return;
738
739 switch (op) {
740 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono224b4e22008-06-19 14:10:28 +0000741 pjsua_var.calls[id].med_tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000742 break;
743 case PJ_ICE_STRANS_OP_NEGOTIATION:
744 if (result != PJ_SUCCESS) {
745 pjsua_var.calls[id].media_st = PJSUA_CALL_MEDIA_ERROR;
746 pjsua_var.calls[id].media_dir = PJMEDIA_DIR_NONE;
747
748 if (pjsua_var.ua_cfg.cb.on_call_media_state) {
749 pjsua_var.ua_cfg.cb.on_call_media_state(id);
750 }
751 }
752 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000753 }
754}
755
756
Benny Prijonof76e1392008-06-06 14:51:48 +0000757/* Parse "HOST:PORT" format */
758static pj_status_t parse_host_port(const pj_str_t *host_port,
759 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000760{
Benny Prijonof76e1392008-06-06 14:51:48 +0000761 pj_str_t str_port;
762
763 str_port.ptr = pj_strchr(host_port, ':');
764 if (str_port.ptr != NULL) {
765 int iport;
766
767 host->ptr = host_port->ptr;
768 host->slen = (str_port.ptr - host->ptr);
769 str_port.ptr++;
770 str_port.slen = host_port->slen - host->slen - 1;
771 iport = (int)pj_strtoul(&str_port);
772 if (iport < 1 || iport > 65535)
773 return PJ_EINVAL;
774 *port = (pj_uint16_t)iport;
775 } else {
776 *host = *host_port;
777 *port = 0;
778 }
779
780 return PJ_SUCCESS;
781}
782
783/* Create ICE media transports (when ice is enabled) */
784static pj_status_t create_ice_media_transports(void)
785{
786 char stunip[PJ_INET6_ADDRSTRLEN];
787 pj_ice_strans_cfg ice_cfg;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000788 unsigned i;
789 pj_status_t status;
790
Benny Prijonoda9785b2007-04-02 20:43:06 +0000791 /* Make sure STUN server resolution has completed */
792 status = pjsua_resolve_stun_server(PJ_TRUE);
793 if (status != PJ_SUCCESS) {
794 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
795 return status;
796 }
797
Benny Prijonof76e1392008-06-06 14:51:48 +0000798 /* Create ICE stream transport configuration */
799 pj_ice_strans_cfg_default(&ice_cfg);
800 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
801 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
802 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
803
804 ice_cfg.af = pj_AF_INET();
805 ice_cfg.resolver = pjsua_var.resolver;
806
Benny Prijono329d6382009-05-29 13:04:03 +0000807 ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
808
Benny Prijonof76e1392008-06-06 14:51:48 +0000809 /* Configure STUN settings */
810 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
811 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
812 ice_cfg.stun.server = pj_str(stunip);
813 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
814 }
Benny Prijono329d6382009-05-29 13:04:03 +0000815 if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
816 ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
Benny Prijonof76e1392008-06-06 14:51:48 +0000817
818 /* Configure TURN settings */
819 if (pjsua_var.media_cfg.enable_turn) {
820 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
821 &ice_cfg.turn.server,
822 &ice_cfg.turn.port);
823 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
824 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
825 return PJ_EINVAL;
826 }
827 if (ice_cfg.turn.port == 0)
828 ice_cfg.turn.port = 3479;
829 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
830 pj_memcpy(&ice_cfg.turn.auth_cred,
831 &pjsua_var.media_cfg.turn_auth_cred,
832 sizeof(ice_cfg.turn.auth_cred));
833 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000834
Benny Prijonoc97608e2007-03-23 16:34:20 +0000835 /* Create each media transport */
836 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono096c56c2007-09-15 08:30:16 +0000837 pjmedia_ice_cb ice_cb;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000838 char name[32];
Benny Prijonof76e1392008-06-06 14:51:48 +0000839 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000840
Benny Prijono096c56c2007-09-15 08:30:16 +0000841 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
842 ice_cb.on_ice_complete = &on_ice_complete;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000843 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i);
Benny Prijono224b4e22008-06-19 14:10:28 +0000844 pjsua_var.calls[i].med_tp_ready = PJ_EPENDING;
Benny Prijonof76e1392008-06-06 14:51:48 +0000845
846 comp_cnt = 1;
Benny Prijono551af422008-08-07 09:55:52 +0000847 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
Benny Prijonof76e1392008-06-06 14:51:48 +0000848 ++comp_cnt;
849
850 status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
851 &ice_cfg, &ice_cb,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000852 &pjsua_var.calls[i].med_tp);
853 if (status != PJ_SUCCESS) {
854 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
855 status);
856 goto on_error;
857 }
858
Benny Prijonof76e1392008-06-06 14:51:48 +0000859 /* Wait until transport is initialized, or time out */
860 PJSUA_UNLOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000861 while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000862 pjsua_handle_events(100);
863 }
864 PJSUA_LOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000865 if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000866 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
Benny Prijono224b4e22008-06-19 14:10:28 +0000867 pjsua_var.calls[i].med_tp_ready);
868 status = pjsua_var.calls[i].med_tp_ready;
Benny Prijonof76e1392008-06-06 14:51:48 +0000869 goto on_error;
870 }
871
Benny Prijonod8179652008-01-23 20:39:07 +0000872 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
873 PJMEDIA_DIR_ENCODING,
874 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono11da9bc2007-09-15 08:55:00 +0000875
Benny Prijonod8179652008-01-23 20:39:07 +0000876 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
877 PJMEDIA_DIR_DECODING,
878 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000879 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000880
881 return PJ_SUCCESS;
882
883on_error:
884 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
885 if (pjsua_var.calls[i].med_tp != NULL) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000886 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000887 pjsua_var.calls[i].med_tp = NULL;
888 }
889 }
890
Benny Prijonoc97608e2007-03-23 16:34:20 +0000891 return status;
892}
893
894
895/*
896 * Create UDP media transports for all the calls. This function creates
897 * one UDP media transport for each call.
898 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +0000899PJ_DEF(pj_status_t) pjsua_media_transports_create(
900 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000901{
902 pjsua_transport_config cfg;
903 unsigned i;
904 pj_status_t status;
905
906
907 /* Make sure pjsua_init() has been called */
908 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
909
910 PJSUA_LOCK();
911
912 /* Delete existing media transports */
913 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono40860c32008-09-04 13:55:33 +0000914 if (pjsua_var.calls[i].med_tp != NULL &&
915 pjsua_var.calls[i].med_tp_auto_del)
916 {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000917 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
918 pjsua_var.calls[i].med_tp = NULL;
Nanang Izzuddind704a8b2008-09-23 16:34:07 +0000919 pjsua_var.calls[i].med_orig = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000920 }
921 }
922
923 /* Copy config */
924 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
925
Benny Prijono40860c32008-09-04 13:55:33 +0000926 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000927 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000928 status = create_ice_media_transports();
Benny Prijonoc97608e2007-03-23 16:34:20 +0000929 } else {
930 status = create_udp_media_transports(&cfg);
931 }
932
Benny Prijono40860c32008-09-04 13:55:33 +0000933 /* Set media transport auto_delete to True */
934 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
935 pjsua_var.calls[i].med_tp_auto_del = PJ_TRUE;
936 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000937
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000938 PJSUA_UNLOCK();
939
940 return status;
941}
942
Benny Prijono40860c32008-09-04 13:55:33 +0000943/*
944 * Attach application's created media transports.
945 */
946PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
947 unsigned count,
948 pj_bool_t auto_delete)
949{
950 unsigned i;
951
952 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
953
954 /* Assign the media transports */
955 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
956 if (pjsua_var.calls[i].med_tp != NULL &&
957 pjsua_var.calls[i].med_tp_auto_del)
958 {
959 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
960 }
961
962 pjsua_var.calls[i].med_tp = tp[i].transport;
963 pjsua_var.calls[i].med_tp_auto_del = auto_delete;
964 }
965
966 return PJ_SUCCESS;
967}
968
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000969
Benny Prijonoc97608e2007-03-23 16:34:20 +0000970pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
Benny Prijonod8179652008-01-23 20:39:07 +0000971 pjsip_role_e role,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000972 int security_level,
Benny Prijono224b4e22008-06-19 14:10:28 +0000973 pj_pool_t *tmp_pool,
974 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000975 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000976{
977 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono224b4e22008-06-19 14:10:28 +0000978 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000979
Benny Prijonod8179652008-01-23 20:39:07 +0000980#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
981 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
982 pjmedia_srtp_setting srtp_opt;
Benny Prijonoa310bd22008-06-27 21:19:44 +0000983 pjmedia_transport *srtp = NULL;
Benny Prijonod8179652008-01-23 20:39:07 +0000984#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000985
Benny Prijonod8179652008-01-23 20:39:07 +0000986 PJ_UNUSED_ARG(role);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000987
Benny Prijonod8179652008-01-23 20:39:07 +0000988 /* Return error if media transport has not been created yet
989 * (e.g. application is starting)
990 */
991 if (call->med_tp == NULL) {
Benny Prijono03789052008-09-16 14:30:50 +0000992 if (sip_err_code)
993 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
Benny Prijonod8179652008-01-23 20:39:07 +0000994 return PJ_EBUSY;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000995 }
996
Benny Prijonod8179652008-01-23 20:39:07 +0000997#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Benny Prijono53a7c702008-04-14 02:57:29 +0000998 /* This function may be called when SRTP transport already exists
999 * (e.g: in re-invite, update), don't need to destroy/re-create.
1000 */
1001 if (!call->med_orig || call->med_tp == call->med_orig) {
1002
1003 /* Check if SRTP requires secure signaling */
1004 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
1005 if (security_level < acc->cfg.srtp_secure_signaling) {
1006 if (sip_err_code)
1007 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1008 return PJSIP_ESESSIONINSECURE;
1009 }
Benny Prijonod8179652008-01-23 20:39:07 +00001010 }
Benny Prijonod8179652008-01-23 20:39:07 +00001011
Benny Prijono53a7c702008-04-14 02:57:29 +00001012 /* Always create SRTP adapter */
1013 pjmedia_srtp_setting_default(&srtp_opt);
1014 srtp_opt.close_member_tp = PJ_FALSE;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001015 /* If media session has been ever established, let's use remote's
1016 * preference in SRTP usage policy, especially when it is stricter.
1017 */
1018 if (call->rem_srtp_use > acc->cfg.use_srtp)
1019 srtp_opt.use = call->rem_srtp_use;
1020 else
1021 srtp_opt.use = acc->cfg.use_srtp;
1022
Benny Prijono53a7c702008-04-14 02:57:29 +00001023 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
1024 call->med_tp,
1025 &srtp_opt, &srtp);
1026 if (status != PJ_SUCCESS) {
1027 if (sip_err_code)
1028 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
1029 return status;
1030 }
Benny Prijonod8179652008-01-23 20:39:07 +00001031
Benny Prijono53a7c702008-04-14 02:57:29 +00001032 /* Set SRTP as current media transport */
1033 call->med_orig = call->med_tp;
1034 call->med_tp = srtp;
1035 }
Benny Prijonod8179652008-01-23 20:39:07 +00001036#else
1037 call->med_orig = call->med_tp;
1038 PJ_UNUSED_ARG(security_level);
1039#endif
1040
Benny Prijonoa310bd22008-06-27 21:19:44 +00001041 /* Find out which media line in SDP that we support. If we are offerer,
1042 * audio will be at index 0 in SDP.
1043 */
1044 if (rem_sdp == 0) {
1045 call->audio_idx = 0;
1046 }
1047 /* Otherwise find out the candidate audio media line in SDP */
1048 else {
1049 unsigned i;
1050 pj_bool_t srtp_active;
1051
1052#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1053 srtp_active = acc->cfg.use_srtp && srtp != NULL;
1054#else
1055 srtp_active = PJ_FALSE;
1056#endif
1057
1058 /* Media count must have been checked */
1059 pj_assert(rem_sdp->media_count != 0);
1060
1061 for (i=0; i<rem_sdp->media_count; ++i) {
1062 const pjmedia_sdp_media *m = rem_sdp->media[i];
1063
1064 /* Skip if media is not audio */
1065 if (pj_stricmp2(&m->desc.media, "audio") != 0)
1066 continue;
1067
1068 /* Skip if media is disabled */
1069 if (m->desc.port == 0)
1070 continue;
1071
1072 /* Skip if transport is not supported */
1073 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") != 0 &&
1074 pj_stricmp2(&m->desc.transport, "RTP/SAVP") != 0)
1075 {
1076 continue;
1077 }
1078
1079 if (call->audio_idx == -1) {
1080 call->audio_idx = i;
1081 } else {
1082 /* We've found multiple candidates. This could happen
1083 * e.g. when remote is offering both RTP/AVP and RTP/AVP,
1084 * or when remote for some reason offers two audio.
1085 */
1086
1087 if (srtp_active &&
1088 pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0)
1089 {
1090 /* Prefer RTP/SAVP when our media transport is SRTP */
1091 call->audio_idx = i;
1092 } else if (!srtp_active &&
1093 pj_stricmp2(&m->desc.transport, "RTP/AVP")==0)
1094 {
1095 /* Prefer RTP/AVP when our media transport is NOT SRTP */
1096 call->audio_idx = i;
1097 }
1098 }
1099 }
1100 }
1101
1102 /* Reject offer if we couldn't find a good m=audio line in offer */
1103 if (call->audio_idx < 0) {
Benny Prijonoab8dba92008-06-27 21:59:15 +00001104 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001105 pjsua_media_channel_deinit(call_id);
Benny Prijonoab8dba92008-06-27 21:59:15 +00001106 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001107 }
1108
1109 PJ_LOG(4,(THIS_FILE, "Media index %d selected for call %d",
1110 call->audio_idx, call->index));
1111
Benny Prijono224b4e22008-06-19 14:10:28 +00001112 /* Create the media transport */
1113 status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001114 rem_sdp, call->audio_idx);
Benny Prijono224b4e22008-06-19 14:10:28 +00001115 if (status != PJ_SUCCESS) {
1116 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1117 pjsua_media_channel_deinit(call_id);
1118 return status;
1119 }
1120
1121 call->med_tp_st = PJSUA_MED_TP_INIT;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001122 return PJ_SUCCESS;
1123}
1124
1125pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1126 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001127 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001128 pjmedia_sdp_session **p_sdp,
1129 int *sip_status_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001130{
Benny Prijonoa310bd22008-06-27 21:19:44 +00001131 enum { MAX_MEDIA = 1 };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001132 pjmedia_sdp_session *sdp;
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001133 pjmedia_transport_info tpinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001134 pjsua_call *call = &pjsua_var.calls[call_id];
1135 pj_status_t status;
1136
Benny Prijono55e82352007-05-10 20:49:08 +00001137 /* Return error if media transport has not been created yet
1138 * (e.g. application is starting)
1139 */
1140 if (call->med_tp == NULL) {
1141 return PJ_EBUSY;
1142 }
1143
Benny Prijonoa310bd22008-06-27 21:19:44 +00001144 /* Media index must have been determined before */
1145 pj_assert(call->audio_idx != -1);
1146
Benny Prijono224b4e22008-06-19 14:10:28 +00001147 /* Create media if it's not created. This could happen when call is
1148 * currently on-hold
1149 */
1150 if (call->med_tp_st == PJSUA_MED_TP_IDLE) {
1151 pjsip_role_e role;
1152 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1153 status = pjsua_media_channel_init(call_id, role, call->secure_level,
1154 pool, rem_sdp, sip_status_code);
1155 if (status != PJ_SUCCESS)
1156 return status;
1157 }
1158
Benny Prijono617c5bc2007-04-02 19:51:21 +00001159 /* Get media socket info */
Benny Prijono734fc2d2008-03-17 16:05:35 +00001160 pjmedia_transport_info_init(&tpinfo);
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001161 pjmedia_transport_get_info(call->med_tp, &tpinfo);
Benny Prijono617c5bc2007-04-02 19:51:21 +00001162
1163 /* Create SDP */
Benny Prijonod8179652008-01-23 20:39:07 +00001164 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA,
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001165 &tpinfo.sock_info, &sdp);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001166 if (status != PJ_SUCCESS) {
1167 if (sip_status_code) *sip_status_code = 500;
Benny Prijono224b4e22008-06-19 14:10:28 +00001168 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001169 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001170
Benny Prijonoa310bd22008-06-27 21:19:44 +00001171 /* If we're answering and the selected media is not the first media
1172 * in SDP, then fill in the unselected media with with zero port.
1173 * Otherwise we'll crash in transport_encode_sdp() because the media
1174 * lines are not aligned between offer and answer.
1175 */
1176 if (rem_sdp && call->audio_idx != 0) {
1177 unsigned i;
1178
1179 for (i=0; i<rem_sdp->media_count; ++i) {
1180 const pjmedia_sdp_media *rem_m = rem_sdp->media[i];
1181 pjmedia_sdp_media *m;
1182 const pjmedia_sdp_attr *a;
1183
1184 if ((int)i == call->audio_idx)
1185 continue;
1186
1187 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1188 pj_strdup(pool, &m->desc.media, &rem_m->desc.media);
1189 pj_strdup(pool, &m->desc.transport, &rem_m->desc.transport);
1190 m->desc.port = 0;
1191
1192 /* Add one format, copy from the offer. And copy the corresponding
1193 * rtpmap and fmtp attributes too.
1194 */
1195 m->desc.fmt_count = 1;
1196 pj_strdup(pool, &m->desc.fmt[0], &rem_m->desc.fmt[0]);
1197 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1198 "rtpmap", &m->desc.fmt[0])) != NULL)
1199 {
1200 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1201 }
1202 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1203 "fmtp", &m->desc.fmt[0])) != NULL)
1204 {
1205 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1206 }
1207
1208 if (i==sdp->media_count)
1209 sdp->media[sdp->media_count++] = m;
1210 else {
1211 pj_array_insert(sdp->media, sizeof(sdp->media[0]),
1212 sdp->media_count, i, &m);
1213 ++sdp->media_count;
1214 }
1215 }
1216 }
1217
Benny Prijono6ba8c542007-10-16 01:34:14 +00001218 /* Add NAT info in the SDP */
1219 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1220 pjmedia_sdp_attr *a;
1221 pj_str_t value;
1222 char nat_info[80];
1223
1224 value.ptr = nat_info;
1225 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1226 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1227 "%d", pjsua_var.nat_type);
1228 } else {
1229 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1230 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1231 "%d %s",
1232 pjsua_var.nat_type,
1233 type_name);
1234 }
1235
1236 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1237
1238 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1239
1240 }
1241
Benny Prijonod8179652008-01-23 20:39:07 +00001242 /* Give the SDP to media transport */
Benny Prijono224b4e22008-06-19 14:10:28 +00001243 status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001244 call->audio_idx);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001245 if (status != PJ_SUCCESS) {
1246 if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono224b4e22008-06-19 14:10:28 +00001247 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001248 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001249
1250 *p_sdp = sdp;
1251 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001252}
1253
1254
1255static void stop_media_session(pjsua_call_id call_id)
1256{
1257 pjsua_call *call = &pjsua_var.calls[call_id];
1258
1259 if (call->conf_slot != PJSUA_INVALID_ID) {
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001260 if (pjsua_var.mconf) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001261 pjsua_conf_remove_port(call->conf_slot);
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001262 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001263 call->conf_slot = PJSUA_INVALID_ID;
1264 }
1265
1266 if (call->session) {
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001267 pjmedia_rtcp_stat stat;
1268
Nanang Izzuddin437d77c2008-08-26 18:04:15 +00001269 if (pjmedia_session_get_stream_stat(call->session, 0, &stat)
1270 == PJ_SUCCESS)
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001271 {
1272 /* Save RTP timestamp & sequence, so when media session is
1273 * restarted, those values will be restored as the initial
1274 * RTP timestamp & sequence of the new media session. So in
1275 * the same call session, RTP timestamp and sequence are
1276 * guaranteed to be contigue.
1277 */
1278 call->rtp_tx_seq_ts_set = 1 | (1 << 1);
1279 call->rtp_tx_seq = stat.rtp_tx_last_seq;
1280 call->rtp_tx_ts = stat.rtp_tx_last_ts;
1281 }
1282
Benny Prijonofc13bf62008-02-20 08:56:15 +00001283 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1284 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0);
1285 }
1286
Benny Prijonoc97608e2007-03-23 16:34:20 +00001287 pjmedia_session_destroy(call->session);
1288 call->session = NULL;
1289
1290 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
1291 call_id));
1292
1293 }
1294
1295 call->media_st = PJSUA_CALL_MEDIA_NONE;
1296}
1297
1298pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1299{
1300 pjsua_call *call = &pjsua_var.calls[call_id];
1301
1302 stop_media_session(call_id);
1303
Benny Prijono224b4e22008-06-19 14:10:28 +00001304 if (call->med_tp_st != PJSUA_MED_TP_IDLE) {
1305 pjmedia_transport_media_stop(call->med_tp);
1306 call->med_tp_st = PJSUA_MED_TP_IDLE;
1307 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001308
Benny Prijono311b63f2008-07-14 11:31:40 +00001309 if (call->med_orig && call->med_tp && call->med_tp != call->med_orig) {
Benny Prijonod8179652008-01-23 20:39:07 +00001310 pjmedia_transport_close(call->med_tp);
1311 call->med_tp = call->med_orig;
1312 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00001313
1314 check_snd_dev_idle();
1315
Benny Prijonoc97608e2007-03-23 16:34:20 +00001316 return PJ_SUCCESS;
1317}
1318
1319
1320/*
1321 * DTMF callback from the stream.
1322 */
1323static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1324 int digit)
1325{
1326 PJ_UNUSED_ARG(strm);
1327
Benny Prijono0c068262008-02-14 14:38:52 +00001328 /* For discussions about call mutex protection related to this
1329 * callback, please see ticket #460:
1330 * http://trac.pjsip.org/repos/ticket/460#comment:4
1331 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001332 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1333 pjsua_call_id call_id;
1334
Benny Prijonod8179652008-01-23 20:39:07 +00001335 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001336 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1337 }
1338}
1339
1340
1341pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijono224b4e22008-06-19 14:10:28 +00001342 const pjmedia_sdp_session *local_sdp,
Benny Prijonodbce2cf2007-03-28 16:24:00 +00001343 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001344{
1345 int prev_media_st = 0;
1346 pjsua_call *call = &pjsua_var.calls[call_id];
1347 pjmedia_session_info sess_info;
Benny Prijono91e567e2007-12-28 08:51:58 +00001348 pjmedia_stream_info *si = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001349 pjmedia_port *media_port;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001350 pj_status_t status;
1351
1352 /* Destroy existing media session, if any. */
1353 prev_media_st = call->media_st;
1354 stop_media_session(call->index);
1355
1356 /* Create media session info based on SDP parameters.
Benny Prijonoc97608e2007-03-23 16:34:20 +00001357 */
1358 status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
1359 pjsua_var.med_endpt,
Benny Prijono91e567e2007-12-28 08:51:58 +00001360 PJMEDIA_MAX_SDP_MEDIA, &sess_info,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001361 local_sdp, remote_sdp);
1362 if (status != PJ_SUCCESS)
1363 return status;
1364
Benny Prijonoa310bd22008-06-27 21:19:44 +00001365 /* Find which session is audio */
1366 PJ_ASSERT_RETURN(call->audio_idx != -1, PJ_EBUG);
1367 PJ_ASSERT_RETURN(call->audio_idx < (int)sess_info.stream_cnt, PJ_EBUG);
1368 si = &sess_info.stream_info[call->audio_idx];
Benny Prijono91e567e2007-12-28 08:51:58 +00001369
1370 /* Reset session info with only one media stream */
1371 sess_info.stream_cnt = 1;
1372 if (si != &sess_info.stream_info[0])
1373 pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001374
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001375 /* Check if no media is active */
Benny Prijono91e567e2007-12-28 08:51:58 +00001376 if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001377 {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001378 /* Call media state */
1379 call->media_st = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001380
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001381 /* Call media direction */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001382 call->media_dir = PJMEDIA_DIR_NONE;
1383
Benny Prijonod8179652008-01-23 20:39:07 +00001384 /* Shutdown transport's session */
1385 pjmedia_transport_media_stop(call->med_tp);
Benny Prijono224b4e22008-06-19 14:10:28 +00001386 call->med_tp_st = PJSUA_MED_TP_IDLE;
Benny Prijono667952e2007-04-02 19:27:54 +00001387
Benny Prijonoc97608e2007-03-23 16:34:20 +00001388 /* No need because we need keepalive? */
1389
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001390 /* Close upper entry of transport stack */
1391 if (call->med_orig && (call->med_tp != call->med_orig)) {
1392 pjmedia_transport_close(call->med_tp);
1393 call->med_tp = call->med_orig;
1394 }
1395
Benny Prijonoc97608e2007-03-23 16:34:20 +00001396 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001397 pjmedia_transport_info tp_info;
1398
Benny Prijono224b4e22008-06-19 14:10:28 +00001399 /* Start/restart media transport */
Benny Prijonod8179652008-01-23 20:39:07 +00001400 status = pjmedia_transport_media_start(call->med_tp,
1401 call->inv->pool,
1402 local_sdp, remote_sdp, 0);
1403 if (status != PJ_SUCCESS)
1404 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001405
Benny Prijono224b4e22008-06-19 14:10:28 +00001406 call->med_tp_st = PJSUA_MED_TP_RUNNING;
1407
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001408 /* Get remote SRTP usage policy */
1409 pjmedia_transport_info_init(&tp_info);
1410 pjmedia_transport_get_info(call->med_tp, &tp_info);
1411 if (tp_info.specific_info_cnt > 0) {
1412 int i;
1413 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1414 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1415 {
1416 pjmedia_srtp_info *srtp_info =
1417 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1418
1419 call->rem_srtp_use = srtp_info->peer_use;
1420 break;
1421 }
1422 }
1423 }
1424
Benny Prijonoc97608e2007-03-23 16:34:20 +00001425 /* Override ptime, if this option is specified. */
1426 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001427 si->param->setting.frm_per_pkt = (pj_uint8_t)
1428 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1429 if (si->param->setting.frm_per_pkt == 0)
1430 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001431 }
1432
1433 /* Disable VAD, if this option is specified. */
1434 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001435 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001436 }
1437
1438
1439 /* Optionally, application may modify other stream settings here
1440 * (such as jitter buffer parameters, codec ptime, etc.)
1441 */
Benny Prijono91e567e2007-12-28 08:51:58 +00001442 si->jb_init = pjsua_var.media_cfg.jb_init;
1443 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1444 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1445 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001446
Benny Prijono8147f402007-11-21 14:50:07 +00001447 /* Set SSRC */
Benny Prijono91e567e2007-12-28 08:51:58 +00001448 si->ssrc = call->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00001449
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001450 /* Set RTP timestamp & sequence, normally these value are intialized
1451 * automatically when stream session created, but for some cases (e.g:
1452 * call reinvite, call update) timestamp and sequence need to be kept
1453 * contigue.
1454 */
1455 si->rtp_ts = call->rtp_tx_ts;
1456 si->rtp_seq = call->rtp_tx_seq;
1457 si->rtp_seq_ts_set = call->rtp_tx_seq_ts_set;
1458
Benny Prijonoc97608e2007-03-23 16:34:20 +00001459 /* Create session based on session info. */
1460 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
1461 &call->med_tp,
1462 call, &call->session );
1463 if (status != PJ_SUCCESS) {
1464 return status;
1465 }
1466
1467 /* If DTMF callback is installed by application, install our
1468 * callback to the session.
1469 */
1470 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1471 pjmedia_session_set_dtmf_callback(call->session, 0,
1472 &dtmf_callback,
Benny Prijonod8179652008-01-23 20:39:07 +00001473 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001474 }
1475
1476 /* Get the port interface of the first stream in the session.
1477 * We need the port interface to add to the conference bridge.
1478 */
1479 pjmedia_session_get_port(call->session, 0, &media_port);
1480
Benny Prijonofc13bf62008-02-20 08:56:15 +00001481 /* Notify application about stream creation.
1482 * Note: application may modify media_port to point to different
1483 * media port
1484 */
1485 if (pjsua_var.ua_cfg.cb.on_stream_created) {
1486 pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session,
1487 0, &media_port);
1488 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001489
1490 /*
1491 * Add the call to conference bridge.
1492 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001493 {
1494 char tmp[PJSIP_MAX_URL_SIZE];
1495 pj_str_t port_name;
1496
1497 port_name.ptr = tmp;
1498 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1499 call->inv->dlg->remote.info->uri,
1500 tmp, sizeof(tmp));
1501 if (port_name.slen < 1) {
1502 port_name = pj_str("call");
1503 }
1504 status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
1505 media_port,
1506 &port_name,
1507 (unsigned*)&call->conf_slot);
1508 if (status != PJ_SUCCESS) {
1509 return status;
1510 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001511 }
1512
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001513 /* Call media direction */
Benny Prijono91e567e2007-12-28 08:51:58 +00001514 call->media_dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001515
1516 /* Call media state */
1517 if (call->local_hold)
1518 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
1519 else if (call->media_dir == PJMEDIA_DIR_DECODING)
1520 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
1521 else
1522 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001523 }
1524
1525 /* Print info. */
1526 {
1527 char info[80];
1528 int info_len = 0;
1529 unsigned i;
1530
1531 for (i=0; i<sess_info.stream_cnt; ++i) {
1532 int len;
1533 const char *dir;
1534 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1535
1536 switch (strm_info->dir) {
1537 case PJMEDIA_DIR_NONE:
1538 dir = "inactive";
1539 break;
1540 case PJMEDIA_DIR_ENCODING:
1541 dir = "sendonly";
1542 break;
1543 case PJMEDIA_DIR_DECODING:
1544 dir = "recvonly";
1545 break;
1546 case PJMEDIA_DIR_ENCODING_DECODING:
1547 dir = "sendrecv";
1548 break;
1549 default:
1550 dir = "unknown";
1551 break;
1552 }
1553 len = pj_ansi_sprintf( info+info_len,
1554 ", stream #%d: %.*s (%s)", i,
1555 (int)strm_info->fmt.encoding_name.slen,
1556 strm_info->fmt.encoding_name.ptr,
1557 dir);
1558 if (len > 0)
1559 info_len += len;
1560 }
1561 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1562 }
1563
1564 return PJ_SUCCESS;
1565}
1566
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001567/*
1568 * Get maxinum number of conference ports.
1569 */
1570PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1571{
1572 return pjsua_var.media_cfg.max_media_ports;
1573}
1574
1575
1576/*
1577 * Get current number of active ports in the bridge.
1578 */
1579PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1580{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001581 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001582 unsigned count = PJ_ARRAY_SIZE(ports);
1583 pj_status_t status;
1584
1585 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1586 if (status != PJ_SUCCESS)
1587 count = 0;
1588
1589 return count;
1590}
1591
1592
1593/*
1594 * Enumerate all conference ports.
1595 */
1596PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1597 unsigned *count)
1598{
1599 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1600}
1601
1602
1603/*
1604 * Get information about the specified conference port
1605 */
1606PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1607 pjsua_conf_port_info *info)
1608{
1609 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001610 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001611 pj_status_t status;
1612
1613 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1614 if (status != PJ_SUCCESS)
1615 return status;
1616
Benny Prijonoac623b32006-07-03 15:19:31 +00001617 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001618 info->slot_id = id;
1619 info->name = cinfo.name;
1620 info->clock_rate = cinfo.clock_rate;
1621 info->channel_count = cinfo.channel_count;
1622 info->samples_per_frame = cinfo.samples_per_frame;
1623 info->bits_per_sample = cinfo.bits_per_sample;
1624
1625 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001626 info->listener_cnt = cinfo.listener_cnt;
1627 for (i=0; i<cinfo.listener_cnt; ++i) {
1628 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001629 }
1630
1631 return PJ_SUCCESS;
1632}
1633
1634
1635/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001636 * Add arbitrary media port to PJSUA's conference bridge.
1637 */
1638PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1639 pjmedia_port *port,
1640 pjsua_conf_port_id *p_id)
1641{
1642 pj_status_t status;
1643
1644 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1645 port, NULL, (unsigned*)p_id);
1646 if (status != PJ_SUCCESS) {
1647 if (p_id)
1648 *p_id = PJSUA_INVALID_ID;
1649 }
1650
1651 return status;
1652}
1653
1654
1655/*
1656 * Remove arbitrary slot from the conference bridge.
1657 */
1658PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1659{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001660 pj_status_t status;
1661
1662 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1663 check_snd_dev_idle();
1664
1665 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00001666}
1667
1668
1669/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001670 * Establish unidirectional media flow from souce to sink.
1671 */
1672PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1673 pjsua_conf_port_id sink)
1674{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001675 /* If sound device idle timer is active, cancel it first. */
Benny Prijono0f711b42009-05-06 19:08:43 +00001676 PJSUA_LOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001677 if (pjsua_var.snd_idle_timer.id) {
1678 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
1679 pjsua_var.snd_idle_timer.id = PJ_FALSE;
1680 }
Benny Prijono0f711b42009-05-06 19:08:43 +00001681 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001682
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001683
Benny Prijonof798e502009-03-09 13:08:16 +00001684 /* For audio switchboard (i.e. APS-Direct):
1685 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001686 * (format, clock rate, channel count) must match to peer's.
1687 * Note that sound device can be reopened only if it doesn't have
1688 * any connection.
1689 */
Benny Prijonof798e502009-03-09 13:08:16 +00001690 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001691 pjmedia_conf_port_info port0_info;
1692 pjmedia_conf_port_info peer_info;
1693 unsigned peer_id;
1694 pj_bool_t need_reopen = PJ_FALSE;
1695 pj_status_t status;
1696
1697 peer_id = (source!=0)? source : sink;
1698 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
1699 &peer_info);
1700 pj_assert(status == PJ_SUCCESS);
1701
1702 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
1703 pj_assert(status == PJ_SUCCESS);
1704
1705 /* Check if sound device is instantiated. */
1706 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1707 !pjsua_var.no_snd);
1708
1709 /* Check if sound device need to reopen because it needs to modify
1710 * settings to match its peer. Sound device must be idle in this case
1711 * though.
1712 */
1713 if (!need_reopen &&
1714 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
1715 {
1716 need_reopen = (peer_info.format.id != port0_info.format.id ||
1717 peer_info.format.bitrate != port0_info.format.bitrate ||
1718 peer_info.clock_rate != port0_info.clock_rate ||
1719 peer_info.channel_count != port0_info.channel_count);
1720 }
1721
1722 if (need_reopen) {
Benny Prijonod65f78c2009-06-03 18:59:37 +00001723 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
1724 pjmedia_aud_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001725
Benny Prijonod65f78c2009-06-03 18:59:37 +00001726 /* Create parameter based on peer info */
1727 status = create_aud_param(&param, pjsua_var.cap_dev,
1728 pjsua_var.play_dev,
1729 peer_info.clock_rate,
1730 peer_info.channel_count,
1731 peer_info.samples_per_frame,
1732 peer_info.bits_per_sample);
1733 if (status != PJ_SUCCESS) {
1734 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1735 return status;
1736 }
Benny Prijonof798e502009-03-09 13:08:16 +00001737
Benny Prijonod65f78c2009-06-03 18:59:37 +00001738 /* And peer format */
1739 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
1740 param.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
1741 param.ext_fmt = peer_info.format;
1742 }
Benny Prijono10454dc2009-02-21 14:21:59 +00001743
Benny Prijonod65f78c2009-06-03 18:59:37 +00001744 status = open_snd_dev(&param);
1745 if (status != PJ_SUCCESS) {
1746 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1747 return status;
1748 }
1749 } else {
1750 /* Null-audio */
1751 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1752 if (status != PJ_SUCCESS) {
1753 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1754 return status;
1755 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001756 }
1757 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001758
Benny Prijonof798e502009-03-09 13:08:16 +00001759 } else {
1760 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001761
Benny Prijonof798e502009-03-09 13:08:16 +00001762 /* Create sound port if none is instantiated */
1763 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1764 !pjsua_var.no_snd)
1765 {
1766 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001767
Benny Prijonof798e502009-03-09 13:08:16 +00001768 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1769 if (status != PJ_SUCCESS) {
1770 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1771 return status;
1772 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001773 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001774
Benny Prijonof798e502009-03-09 13:08:16 +00001775 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001776
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001777 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
1778}
1779
1780
1781/*
1782 * Disconnect media flow from the source to destination port.
1783 */
1784PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
1785 pjsua_conf_port_id sink)
1786{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001787 pj_status_t status;
1788
1789 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001790 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001791
1792 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001793}
1794
1795
Benny Prijono6dd967c2006-12-26 02:27:14 +00001796/*
1797 * Adjust the signal level to be transmitted from the bridge to the
1798 * specified port by making it louder or quieter.
1799 */
1800PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
1801 float level)
1802{
1803 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
1804 (int)((level-1) * 128));
1805}
1806
1807/*
1808 * Adjust the signal level to be received from the specified port (to
1809 * the bridge) by making it louder or quieter.
1810 */
1811PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
1812 float level)
1813{
1814 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
1815 (int)((level-1) * 128));
1816}
1817
1818
1819/*
1820 * Get last signal level transmitted to or received from the specified port.
1821 */
1822PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
1823 unsigned *tx_level,
1824 unsigned *rx_level)
1825{
1826 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
1827 tx_level, rx_level);
1828}
1829
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001830/*****************************************************************************
1831 * File player.
1832 */
1833
Benny Prijonod5696da2007-07-17 16:25:45 +00001834static char* get_basename(const char *path, unsigned len)
1835{
1836 char *p = ((char*)path) + len;
1837
1838 if (len==0)
1839 return p;
1840
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001841 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00001842
1843 return (p==path) ? p : p+1;
1844}
1845
1846
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001847/*
1848 * Create a file player, and automatically connect this player to
1849 * the conference bridge.
1850 */
1851PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1852 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001853 pjsua_player_id *p_id)
1854{
1855 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001856 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00001857 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001858 pjmedia_port *port;
1859 pj_status_t status;
1860
1861 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1862 return PJ_ETOOMANY;
1863
1864 PJSUA_LOCK();
1865
1866 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1867 if (pjsua_var.player[file_id].port == NULL)
1868 break;
1869 }
1870
1871 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1872 /* This is unexpected */
1873 PJSUA_UNLOCK();
1874 pj_assert(0);
1875 return PJ_EBUG;
1876 }
1877
1878 pj_memcpy(path, filename->ptr, filename->slen);
1879 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00001880
1881 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1882 if (!pool) {
1883 PJSUA_UNLOCK();
1884 return PJ_ENOMEM;
1885 }
1886
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00001887 status = pjmedia_wav_player_port_create(
1888 pool, path,
1889 pjsua_var.mconf_cfg.samples_per_frame *
1890 1000 / pjsua_var.media_cfg.channel_count /
1891 pjsua_var.media_cfg.clock_rate,
1892 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001893 if (status != PJ_SUCCESS) {
1894 PJSUA_UNLOCK();
1895 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001896 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001897 return status;
1898 }
1899
Benny Prijono5297af92008-03-18 13:40:40 +00001900 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001901 port, filename, &slot);
1902 if (status != PJ_SUCCESS) {
1903 pjmedia_port_destroy(port);
1904 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00001905 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1906 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001907 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001908 return status;
1909 }
1910
Benny Prijonoa66c3312007-01-21 23:12:40 +00001911 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00001912 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001913 pjsua_var.player[file_id].port = port;
1914 pjsua_var.player[file_id].slot = slot;
1915
1916 if (p_id) *p_id = file_id;
1917
1918 ++pjsua_var.player_cnt;
1919
1920 PJSUA_UNLOCK();
1921 return PJ_SUCCESS;
1922}
1923
1924
1925/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00001926 * Create a file playlist media port, and automatically add the port
1927 * to the conference bridge.
1928 */
1929PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
1930 unsigned file_count,
1931 const pj_str_t *label,
1932 unsigned options,
1933 pjsua_player_id *p_id)
1934{
1935 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00001936 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001937 pjmedia_port *port;
1938 pj_status_t status;
1939
1940 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1941 return PJ_ETOOMANY;
1942
1943 PJSUA_LOCK();
1944
1945 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1946 if (pjsua_var.player[file_id].port == NULL)
1947 break;
1948 }
1949
1950 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1951 /* This is unexpected */
1952 PJSUA_UNLOCK();
1953 pj_assert(0);
1954 return PJ_EBUG;
1955 }
1956
1957
1958 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
1959 pjsua_var.media_cfg.clock_rate;
1960
Benny Prijonod5696da2007-07-17 16:25:45 +00001961 pool = pjsua_pool_create("playlist", 1000, 1000);
1962 if (!pool) {
1963 PJSUA_UNLOCK();
1964 return PJ_ENOMEM;
1965 }
1966
1967 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001968 file_names, file_count,
1969 ptime, options, 0, &port);
1970 if (status != PJ_SUCCESS) {
1971 PJSUA_UNLOCK();
1972 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001973 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001974 return status;
1975 }
1976
Benny Prijonod5696da2007-07-17 16:25:45 +00001977 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001978 port, &port->info.name, &slot);
1979 if (status != PJ_SUCCESS) {
1980 pjmedia_port_destroy(port);
1981 PJSUA_UNLOCK();
1982 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001983 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001984 return status;
1985 }
1986
1987 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00001988 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001989 pjsua_var.player[file_id].port = port;
1990 pjsua_var.player[file_id].slot = slot;
1991
1992 if (p_id) *p_id = file_id;
1993
1994 ++pjsua_var.player_cnt;
1995
1996 PJSUA_UNLOCK();
1997 return PJ_SUCCESS;
1998
1999}
2000
2001
2002/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002003 * Get conference port ID associated with player.
2004 */
2005PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
2006{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002007 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002008 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2009
2010 return pjsua_var.player[id].slot;
2011}
2012
Benny Prijono469b1522006-12-26 03:05:17 +00002013/*
2014 * Get the media port for the player.
2015 */
Benny Prijonobe41d862008-01-18 13:24:28 +00002016PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00002017 pjmedia_port **p_port)
2018{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002019 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002020 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2021 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2022
2023 *p_port = pjsua_var.player[id].port;
2024
2025 return PJ_SUCCESS;
2026}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002027
2028/*
2029 * Set playback position.
2030 */
2031PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
2032 pj_uint32_t samples)
2033{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002034 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002035 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002036 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002037
2038 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
2039}
2040
2041
2042/*
2043 * Close the file, remove the player from the bridge, and free
2044 * resources associated with the file player.
2045 */
2046PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
2047{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002048 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002049 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2050
2051 PJSUA_LOCK();
2052
2053 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002054 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002055 pjmedia_port_destroy(pjsua_var.player[id].port);
2056 pjsua_var.player[id].port = NULL;
2057 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002058 pj_pool_release(pjsua_var.player[id].pool);
2059 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002060 pjsua_var.player_cnt--;
2061 }
2062
2063 PJSUA_UNLOCK();
2064
2065 return PJ_SUCCESS;
2066}
2067
2068
2069/*****************************************************************************
2070 * File recorder.
2071 */
2072
2073/*
2074 * Create a file recorder, and automatically connect this recorder to
2075 * the conference bridge.
2076 */
2077PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00002078 unsigned enc_type,
2079 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002080 pj_ssize_t max_size,
2081 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002082 pjsua_recorder_id *p_id)
2083{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002084 enum Format
2085 {
2086 FMT_UNKNOWN,
2087 FMT_WAV,
2088 FMT_MP3,
2089 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002090 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002091 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002092 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00002093 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00002094 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002095 pjmedia_port *port;
2096 pj_status_t status;
2097
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002098 /* Filename must present */
2099 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
2100
Benny Prijono00cae612006-07-31 15:19:36 +00002101 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002102 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002103
Benny Prijono8f310522006-10-20 11:08:49 +00002104 /* Don't support encoding type at present */
2105 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002106
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002107 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
2108 return PJ_ETOOMANY;
2109
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002110 /* Determine the file format */
2111 ext.ptr = filename->ptr + filename->slen - 4;
2112 ext.slen = 4;
2113
2114 if (pj_stricmp2(&ext, ".wav") == 0)
2115 file_format = FMT_WAV;
2116 else if (pj_stricmp2(&ext, ".mp3") == 0)
2117 file_format = FMT_MP3;
2118 else {
2119 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
2120 "determine file format for %.*s",
2121 (int)filename->slen, filename->ptr));
2122 return PJ_ENOTSUP;
2123 }
2124
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002125 PJSUA_LOCK();
2126
2127 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
2128 if (pjsua_var.recorder[file_id].port == NULL)
2129 break;
2130 }
2131
2132 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
2133 /* This is unexpected */
2134 PJSUA_UNLOCK();
2135 pj_assert(0);
2136 return PJ_EBUG;
2137 }
2138
2139 pj_memcpy(path, filename->ptr, filename->slen);
2140 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002141
Benny Prijonod5696da2007-07-17 16:25:45 +00002142 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2143 if (!pool) {
2144 PJSUA_UNLOCK();
2145 return PJ_ENOMEM;
2146 }
2147
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002148 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00002149 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002150 pjsua_var.media_cfg.clock_rate,
2151 pjsua_var.mconf_cfg.channel_count,
2152 pjsua_var.mconf_cfg.samples_per_frame,
2153 pjsua_var.mconf_cfg.bits_per_sample,
2154 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002155 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00002156 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002157 port = NULL;
2158 status = PJ_ENOTSUP;
2159 }
2160
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002161 if (status != PJ_SUCCESS) {
2162 PJSUA_UNLOCK();
2163 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002164 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002165 return status;
2166 }
2167
Benny Prijonod5696da2007-07-17 16:25:45 +00002168 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002169 port, filename, &slot);
2170 if (status != PJ_SUCCESS) {
2171 pjmedia_port_destroy(port);
2172 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00002173 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002174 return status;
2175 }
2176
2177 pjsua_var.recorder[file_id].port = port;
2178 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00002179 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002180
2181 if (p_id) *p_id = file_id;
2182
2183 ++pjsua_var.rec_cnt;
2184
2185 PJSUA_UNLOCK();
2186 return PJ_SUCCESS;
2187}
2188
2189
2190/*
2191 * Get conference port associated with recorder.
2192 */
2193PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
2194{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002195 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2196 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002197 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2198
2199 return pjsua_var.recorder[id].slot;
2200}
2201
Benny Prijono469b1522006-12-26 03:05:17 +00002202/*
2203 * Get the media port for the recorder.
2204 */
2205PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
2206 pjmedia_port **p_port)
2207{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002208 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2209 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002210 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2211 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2212
2213 *p_port = pjsua_var.recorder[id].port;
2214 return PJ_SUCCESS;
2215}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002216
2217/*
2218 * Destroy recorder (this will complete recording).
2219 */
2220PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
2221{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002222 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2223 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002224 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2225
2226 PJSUA_LOCK();
2227
2228 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002229 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002230 pjmedia_port_destroy(pjsua_var.recorder[id].port);
2231 pjsua_var.recorder[id].port = NULL;
2232 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002233 pj_pool_release(pjsua_var.recorder[id].pool);
2234 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002235 pjsua_var.rec_cnt--;
2236 }
2237
2238 PJSUA_UNLOCK();
2239
2240 return PJ_SUCCESS;
2241}
2242
2243
2244/*****************************************************************************
2245 * Sound devices.
2246 */
2247
2248/*
2249 * Enum sound devices.
2250 */
Benny Prijonof798e502009-03-09 13:08:16 +00002251
2252PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002253 unsigned *count)
2254{
2255 unsigned i, dev_count;
2256
Benny Prijono10454dc2009-02-21 14:21:59 +00002257 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002258
2259 if (dev_count > *count) dev_count = *count;
2260
2261 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00002262 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002263
Benny Prijono10454dc2009-02-21 14:21:59 +00002264 status = pjmedia_aud_dev_get_info(i, &info[i]);
2265 if (status != PJ_SUCCESS)
2266 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002267 }
2268
2269 *count = dev_count;
2270
2271 return PJ_SUCCESS;
2272}
Benny Prijonof798e502009-03-09 13:08:16 +00002273
2274
Benny Prijono10454dc2009-02-21 14:21:59 +00002275PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
2276 unsigned *count)
2277{
2278 unsigned i, dev_count;
2279
2280 dev_count = pjmedia_aud_dev_count();
2281
2282 if (dev_count > *count) dev_count = *count;
2283 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
2284
2285 for (i=0; i<dev_count; ++i) {
2286 pjmedia_aud_dev_info ai;
2287 pj_status_t status;
2288
2289 status = pjmedia_aud_dev_get_info(i, &ai);
2290 if (status != PJ_SUCCESS)
2291 return status;
2292
2293 strncpy(info[i].name, ai.name, sizeof(info[i].name));
2294 info[i].name[sizeof(info[i].name)-1] = '\0';
2295 info[i].input_count = ai.input_count;
2296 info[i].output_count = ai.output_count;
2297 info[i].default_samples_per_sec = ai.default_samples_per_sec;
2298 }
2299
2300 *count = dev_count;
2301
2302 return PJ_SUCCESS;
2303}
Benny Prijono10454dc2009-02-21 14:21:59 +00002304
Benny Prijonof798e502009-03-09 13:08:16 +00002305/* Create audio device parameter to open the device */
2306static pj_status_t create_aud_param(pjmedia_aud_param *param,
2307 pjmedia_aud_dev_index capture_dev,
2308 pjmedia_aud_dev_index playback_dev,
2309 unsigned clock_rate,
2310 unsigned channel_count,
2311 unsigned samples_per_frame,
2312 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002313{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002314 pj_status_t status;
2315
Benny Prijono96e74f32009-02-22 12:00:12 +00002316 /* Normalize device ID with new convention about default device ID */
2317 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
2318 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
2319
Benny Prijono10454dc2009-02-21 14:21:59 +00002320 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00002321 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00002322 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00002323 pjsua_perror(THIS_FILE, "Error retrieving default audio "
2324 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00002325 return status;
2326 }
Benny Prijonof798e502009-03-09 13:08:16 +00002327 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
2328 param->rec_id = capture_dev;
2329 param->play_id = playback_dev;
2330 param->clock_rate = clock_rate;
2331 param->channel_count = channel_count;
2332 param->samples_per_frame = samples_per_frame;
2333 param->bits_per_sample = bits_per_sample;
2334
2335 /* Update the setting with user preference */
2336#define update_param(cap, field) \
2337 if (pjsua_var.aud_param.flags & cap) { \
2338 param->flags |= cap; \
2339 param->field = pjsua_var.aud_param.field; \
2340 }
2341 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
2342 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
2343 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
2344 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
2345#undef update_param
2346
Benny Prijono10454dc2009-02-21 14:21:59 +00002347 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00002348 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
2349 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
2350 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
2351 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
2352
Benny Prijono10454dc2009-02-21 14:21:59 +00002353 /* EC settings */
2354 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00002355 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
2356 param->ec_enabled = PJ_TRUE;
2357 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00002358 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00002359 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00002360 }
2361
Benny Prijonof798e502009-03-09 13:08:16 +00002362 return PJ_SUCCESS;
2363}
Benny Prijono26056d82006-10-11 16:03:41 +00002364
Benny Prijonof798e502009-03-09 13:08:16 +00002365/* Internal: the first time the audio device is opened (during app
2366 * startup), retrieve the audio settings such as volume level
2367 * so that aud_get_settings() will work.
2368 */
2369static pj_status_t update_initial_aud_param()
2370{
2371 pjmedia_aud_stream *strm;
2372 pjmedia_aud_param param;
2373 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00002374
Benny Prijonof798e502009-03-09 13:08:16 +00002375 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002376
Benny Prijonof798e502009-03-09 13:08:16 +00002377 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00002378
Benny Prijonof798e502009-03-09 13:08:16 +00002379 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002380 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00002381 pjsua_perror(THIS_FILE, "Error audio stream "
2382 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002383 return status;
2384 }
2385
Benny Prijonof798e502009-03-09 13:08:16 +00002386#define update_saved_param(cap, field) \
2387 if (param.flags & cap) { \
2388 pjsua_var.aud_param.flags |= cap; \
2389 pjsua_var.aud_param.field = param.field; \
2390 }
2391
2392 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
2393 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
2394 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
2395 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
2396#undef update_saved_param
2397
2398 return PJ_SUCCESS;
2399}
2400
2401/* Get format name */
2402static const char *get_fmt_name(pj_uint32_t id)
2403{
2404 static char name[8];
2405
2406 if (id == PJMEDIA_FORMAT_L16)
2407 return "PCM";
2408 pj_memcpy(name, &id, 4);
2409 name[4] = '\0';
2410 return name;
2411}
2412
2413/* Open sound device with the setting. */
2414static pj_status_t open_snd_dev(pjmedia_aud_param *param)
2415{
2416 pjmedia_port *conf_port;
2417 pj_status_t status;
2418
2419 PJ_ASSERT_RETURN(param, PJ_EINVAL);
2420
2421 /* Check if NULL sound device is used */
2422 if (NULL_SND_DEV_ID==param->rec_id || NULL_SND_DEV_ID==param->play_id) {
2423 return pjsua_set_null_snd_dev();
2424 }
2425
2426 /* Close existing sound port */
2427 close_snd_dev();
2428
2429 /* Create memory pool for sound device. */
2430 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2431 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2432
2433
2434 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
2435 get_fmt_name(param->ext_fmt.id),
2436 param->clock_rate, param->channel_count,
2437 param->samples_per_frame / param->channel_count * 1000 /
2438 param->clock_rate));
2439
2440 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
2441 param, &pjsua_var.snd_port);
2442 if (status != PJ_SUCCESS)
2443 return status;
2444
2445 /* Get the port0 of the conference bridge. */
2446 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2447 pj_assert(conf_port != NULL);
2448
2449 /* For conference bridge, resample if necessary if the bridge's
2450 * clock rate is different than the sound device's clock rate.
2451 */
2452 if (!pjsua_var.is_mswitch &&
2453 param->ext_fmt.id == PJMEDIA_FORMAT_PCM &&
2454 conf_port->info.clock_rate != param->clock_rate)
2455 {
2456 pjmedia_port *resample_port;
2457 unsigned resample_opt = 0;
2458
2459 if (pjsua_var.media_cfg.quality >= 3 &&
2460 pjsua_var.media_cfg.quality <= 4)
2461 {
2462 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
2463 }
2464 else if (pjsua_var.media_cfg.quality < 3) {
2465 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
2466 }
2467
2468 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
2469 conf_port,
2470 param->clock_rate,
2471 resample_opt,
2472 &resample_port);
2473 if (status != PJ_SUCCESS) {
2474 char errmsg[PJ_ERR_MSG_SIZE];
2475 pj_strerror(status, errmsg, sizeof(errmsg));
2476 PJ_LOG(4, (THIS_FILE,
2477 "Error creating resample port: %s",
2478 errmsg));
2479 close_snd_dev();
2480 return status;
2481 }
2482
2483 conf_port = resample_port;
2484 }
2485
2486 /* Otherwise for audio switchboard, the switch's port0 setting is
2487 * derived from the sound device setting, so update the setting.
2488 */
2489 if (pjsua_var.is_mswitch) {
2490 pj_memcpy(&conf_port->info.format, &param->ext_fmt,
2491 sizeof(conf_port->info.format));
2492 conf_port->info.clock_rate = param->clock_rate;
2493 conf_port->info.samples_per_frame = param->samples_per_frame;
2494 conf_port->info.channel_count = param->channel_count;
2495 conf_port->info.bits_per_sample = 16;
2496 }
2497
2498 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00002499 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
2500 conf_port );
2501 if (status != PJ_SUCCESS) {
2502 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
2503 "sound device", status);
2504 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2505 pjsua_var.snd_port = NULL;
2506 return status;
2507 }
2508
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002509 /* Save the device IDs */
Benny Prijonof798e502009-03-09 13:08:16 +00002510 pjsua_var.cap_dev = param->rec_id;
2511 pjsua_var.play_dev = param->play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002512
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002513 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00002514 {
2515 pjmedia_aud_dev_info rec_info;
2516 pjmedia_aud_stream *strm;
2517 pjmedia_aud_param si;
2518 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002519
Benny Prijonof798e502009-03-09 13:08:16 +00002520 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2521 status = pjmedia_aud_stream_get_param(strm, &si);
2522 if (status == PJ_SUCCESS)
2523 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00002524
Benny Prijonof798e502009-03-09 13:08:16 +00002525 if (status==PJ_SUCCESS) {
2526 if (param->clock_rate != pjsua_var.media_cfg.clock_rate) {
2527 char tmp_buf[128];
2528 int tmp_buf_len = sizeof(tmp_buf);
2529
2530 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
2531 "%s (%dKHz)",
2532 rec_info.name,
2533 param->clock_rate/1000);
2534 pj_strset(&tmp, tmp_buf, tmp_buf_len);
2535 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
2536 } else {
2537 pjmedia_conf_set_port0_name(pjsua_var.mconf,
2538 pj_cstr(&tmp, rec_info.name));
2539 }
2540 }
2541
2542 /* Any error is not major, let it through */
2543 status = PJ_SUCCESS;
2544 };
2545
2546 /* If this is the first time the audio device is open, retrieve some
2547 * settings from the device (such as volume settings) so that the
2548 * pjsua_snd_get_setting() work.
2549 */
2550 if (pjsua_var.aud_open_cnt == 0) {
2551 update_initial_aud_param();
2552 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00002553 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002554
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002555 return PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00002556}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002557
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002558
Benny Prijonof798e502009-03-09 13:08:16 +00002559/* Close existing sound device */
2560static void close_snd_dev(void)
2561{
2562 /* Close sound device */
2563 if (pjsua_var.snd_port) {
2564 pjmedia_aud_dev_info cap_info, play_info;
2565 pjmedia_aud_stream *strm;
2566 pjmedia_aud_param param;
2567
2568 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2569 pjmedia_aud_stream_get_param(strm, &param);
2570
2571 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
2572 cap_info.name[0] = '\0';
2573 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
2574 play_info.name[0] = '\0';
2575
2576 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
2577 "%s sound capture device",
2578 play_info.name, cap_info.name));
2579
2580 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
2581 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2582 pjsua_var.snd_port = NULL;
2583 }
2584
2585 /* Close null sound device */
2586 if (pjsua_var.null_snd) {
2587 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
2588 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
2589 pjsua_var.null_snd = NULL;
2590 }
2591
2592 if (pjsua_var.snd_pool)
2593 pj_pool_release(pjsua_var.snd_pool);
2594 pjsua_var.snd_pool = NULL;
2595}
2596
2597
2598/*
2599 * Select or change sound device. Application may call this function at
2600 * any time to replace current sound device.
2601 */
2602PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
2603 int playback_dev)
2604{
2605 unsigned alt_cr_cnt = 1;
2606 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
2607 unsigned i;
2608 pj_status_t status = -1;
2609
Benny Prijono23ea21a2009-06-03 12:43:06 +00002610 /* Null-sound */
2611 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
2612 return pjsua_set_null_snd_dev();
2613 }
2614
Benny Prijonof798e502009-03-09 13:08:16 +00002615 /* Set default clock rate */
2616 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
2617 if (alt_cr[0] == 0)
2618 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
2619
2620 /* Allow retrying of different clock rate if we're using conference
2621 * bridge (meaning audio format is always PCM), otherwise lock on
2622 * to one clock rate.
2623 */
2624 if (pjsua_var.is_mswitch) {
2625 alt_cr_cnt = 1;
2626 } else {
2627 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
2628 }
2629
2630 /* Attempts to open the sound device with different clock rates */
2631 for (i=0; i<alt_cr_cnt; ++i) {
2632 pjmedia_aud_param param;
2633 unsigned samples_per_frame;
2634
2635 /* Create the default audio param */
2636 samples_per_frame = alt_cr[i] *
2637 pjsua_var.media_cfg.audio_frame_ptime *
2638 pjsua_var.media_cfg.channel_count / 1000;
2639 status = create_aud_param(&param, capture_dev, playback_dev,
2640 alt_cr[i], pjsua_var.media_cfg.channel_count,
2641 samples_per_frame, 16);
2642 if (status != PJ_SUCCESS)
2643 return status;
2644
2645 /* Open! */
2646 status = open_snd_dev(&param);
2647 if (status == PJ_SUCCESS)
2648 break;
2649 }
2650
2651 if (status != PJ_SUCCESS) {
2652 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
2653 return status;
2654 }
2655
2656 return PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002657}
2658
2659
2660/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00002661 * Get currently active sound devices. If sound devices has not been created
2662 * (for example when pjsua_start() is not called), it is possible that
2663 * the function returns PJ_SUCCESS with -1 as device IDs.
2664 */
2665PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
2666 int *playback_dev)
2667{
2668 if (capture_dev) {
2669 *capture_dev = pjsua_var.cap_dev;
2670 }
2671 if (playback_dev) {
2672 *playback_dev = pjsua_var.play_dev;
2673 }
2674
2675 return PJ_SUCCESS;
2676}
2677
2678
2679/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002680 * Use null sound device.
2681 */
2682PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
2683{
2684 pjmedia_port *conf_port;
2685 pj_status_t status;
2686
2687 /* Close existing sound device */
2688 close_snd_dev();
2689
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002690 /* Create memory pool for sound device. */
2691 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2692 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2693
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002694 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
2695
2696 /* Get the port0 of the conference bridge. */
2697 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2698 pj_assert(conf_port != NULL);
2699
2700 /* Create master port, connecting port0 of the conference bridge to
2701 * a null port.
2702 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002703 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002704 conf_port, 0, &pjsua_var.null_snd);
2705 if (status != PJ_SUCCESS) {
2706 pjsua_perror(THIS_FILE, "Unable to create null sound device",
2707 status);
2708 return status;
2709 }
2710
2711 /* Start the master port */
2712 status = pjmedia_master_port_start(pjsua_var.null_snd);
2713 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2714
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002715 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2716 pjsua_var.play_dev = NULL_SND_DEV_ID;
2717
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002718 return PJ_SUCCESS;
2719}
2720
2721
Benny Prijonoe909eac2006-07-27 22:04:56 +00002722
2723/*
2724 * Use no device!
2725 */
2726PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2727{
2728 /* Close existing sound device */
2729 close_snd_dev();
2730
2731 pjsua_var.no_snd = PJ_TRUE;
2732 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2733}
2734
2735
Benny Prijonof20687a2006-08-04 18:27:19 +00002736/*
2737 * Configure the AEC settings of the sound port.
2738 */
Benny Prijono5da50432006-08-07 10:24:52 +00002739PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00002740{
2741 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2742
2743 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00002744 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
2745 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00002746
2747 return PJ_SUCCESS;
2748}
2749
2750
2751/*
2752 * Get current AEC tail length.
2753 */
Benny Prijono22dfe592006-08-06 12:07:13 +00002754PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00002755{
2756 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2757 return PJ_SUCCESS;
2758}
2759
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002760
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002761/*
Benny Prijonof798e502009-03-09 13:08:16 +00002762 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002763 */
Benny Prijonof798e502009-03-09 13:08:16 +00002764PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002765{
Benny Prijonof798e502009-03-09 13:08:16 +00002766 return pjsua_var.snd_port != NULL;
2767}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002768
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002769
Benny Prijonof798e502009-03-09 13:08:16 +00002770/*
2771 * Configure sound device setting to the sound device being used.
2772 */
2773PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
2774 const void *pval,
2775 pj_bool_t keep)
2776{
Benny Prijono09b0ff62009-03-10 12:07:51 +00002777 pj_status_t status;
2778
Benny Prijonof798e502009-03-09 13:08:16 +00002779 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00002780 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00002781 return PJMEDIA_EAUD_INVCAP;
2782 }
2783
Benny Prijono09b0ff62009-03-10 12:07:51 +00002784 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00002785 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00002786 pjmedia_aud_stream *strm;
2787
2788 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00002789 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00002790 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00002791 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00002792 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00002793
2794 if (status != PJ_SUCCESS)
2795 return status;
2796
2797 /* Save in internal param for later device open */
2798 if (keep) {
2799 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
2800 cap, pval);
2801 }
2802
2803 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00002804}
2805
2806/*
2807 * Retrieve a sound device setting.
2808 */
2809PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
2810 void *pval)
2811{
2812 /* If sound device has never been opened before, open it to
2813 * retrieve the initial setting from the device (e.g. audio
2814 * volume)
2815 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00002816 if (pjsua_var.aud_open_cnt==0) {
2817 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00002818 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00002819 close_snd_dev();
2820 }
Benny Prijonof798e502009-03-09 13:08:16 +00002821
2822 if (pjsua_snd_is_active()) {
2823 /* Sound is active, retrieve from device directly */
2824 pjmedia_aud_stream *strm;
2825
2826 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2827 return pjmedia_aud_stream_get_cap(strm, cap, pval);
2828 } else {
2829 /* Otherwise retrieve from internal param */
2830 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
2831 cap, pval);
2832 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002833}
2834
2835
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002836/*****************************************************************************
2837 * Codecs.
2838 */
2839
2840/*
2841 * Enum all supported codecs in the system.
2842 */
2843PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
2844 unsigned *p_count )
2845{
2846 pjmedia_codec_mgr *codec_mgr;
2847 pjmedia_codec_info info[32];
2848 unsigned i, count, prio[32];
2849 pj_status_t status;
2850
2851 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2852 count = PJ_ARRAY_SIZE(info);
2853 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
2854 if (status != PJ_SUCCESS) {
2855 *p_count = 0;
2856 return status;
2857 }
2858
2859 if (count > *p_count) count = *p_count;
2860
2861 for (i=0; i<count; ++i) {
2862 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
2863 id[i].codec_id = pj_str(id[i].buf_);
2864 id[i].priority = (pj_uint8_t) prio[i];
2865 }
2866
2867 *p_count = count;
2868
2869 return PJ_SUCCESS;
2870}
2871
2872
2873/*
2874 * Change codec priority.
2875 */
2876PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
2877 pj_uint8_t priority )
2878{
Benny Prijono88accae2008-06-26 15:48:14 +00002879 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002880 pjmedia_codec_mgr *codec_mgr;
2881
2882 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2883
Benny Prijono88accae2008-06-26 15:48:14 +00002884 if (codec_id->slen==1 && *codec_id->ptr=='*')
2885 codec_id = &all;
2886
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002887 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
2888 priority);
2889}
2890
2891
2892/*
2893 * Get codec parameters.
2894 */
2895PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
2896 pjmedia_codec_param *param )
2897{
Benny Prijono88accae2008-06-26 15:48:14 +00002898 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002899 const pjmedia_codec_info *info;
2900 pjmedia_codec_mgr *codec_mgr;
2901 unsigned count = 1;
2902 pj_status_t status;
2903
2904 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2905
Benny Prijono88accae2008-06-26 15:48:14 +00002906 if (codec_id->slen==1 && *codec_id->ptr=='*')
2907 codec_id = &all;
2908
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002909 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
2910 &count, &info, NULL);
2911 if (status != PJ_SUCCESS)
2912 return status;
2913
2914 if (count != 1)
2915 return PJ_ENOTFOUND;
2916
2917 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
2918 return status;
2919}
2920
2921
2922/*
2923 * Set codec parameters.
2924 */
2925PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
2926 const pjmedia_codec_param *param)
2927{
Benny Prijono00cae612006-07-31 15:19:36 +00002928 PJ_UNUSED_ARG(id);
2929 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002930 PJ_TODO(set_codec_param);
2931 return PJ_SUCCESS;
2932}