blob: fa9a1f9c22f029b8f35893515c4426bf1ae45c8f [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);
79
Benny Prijonoeebe9af2006-06-13 22:57:13 +000080 /* Copy configuration */
Benny Prijonof76e1392008-06-06 14:51:48 +000081 pjsua_media_config_dup(pjsua_var.pool, &pjsua_var.media_cfg, cfg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000082
83 /* Normalize configuration */
Benny Prijono50f19b32008-03-11 13:15:43 +000084 if (pjsua_var.media_cfg.snd_clock_rate == 0) {
85 pjsua_var.media_cfg.snd_clock_rate = pjsua_var.media_cfg.clock_rate;
86 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +000087
88 if (pjsua_var.media_cfg.has_ioqueue &&
89 pjsua_var.media_cfg.thread_cnt == 0)
90 {
91 pjsua_var.media_cfg.thread_cnt = 1;
92 }
93
94 if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
95 pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
96 }
97
98 /* Create media endpoint. */
99 status = pjmedia_endpt_create(&pjsua_var.cp.factory,
100 pjsua_var.media_cfg.has_ioqueue? NULL :
101 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
102 pjsua_var.media_cfg.thread_cnt,
103 &pjsua_var.med_endpt);
104 if (status != PJ_SUCCESS) {
105 pjsua_perror(THIS_FILE,
106 "Media stack initialization has returned error",
107 status);
108 return status;
109 }
110
111 /* Register all codecs */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000112
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000113#if PJMEDIA_HAS_SPEEX_CODEC
114 /* Register speex. */
Nanang Izzuddin9dbad152008-06-10 18:56:10 +0000115 status = pjmedia_codec_speex_init(pjsua_var.med_endpt,
116 0,
117 pjsua_var.media_cfg.quality,
118 -1);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000119 if (status != PJ_SUCCESS) {
120 pjsua_perror(THIS_FILE, "Error initializing Speex codec",
121 status);
122 return status;
123 }
Benny Prijono7ca96da2006-08-07 12:11:40 +0000124
125 /* Set speex/16000 to higher priority*/
126 codec_id = pj_str("speex/16000");
127 pjmedia_codec_mgr_set_codec_priority(
128 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
129 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
130
131 /* Set speex/8000 to next higher priority*/
132 codec_id = pj_str("speex/8000");
133 pjmedia_codec_mgr_set_codec_priority(
134 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
135 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
136
137
138
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000139#endif /* PJMEDIA_HAS_SPEEX_CODEC */
140
Benny Prijono00cae612006-07-31 15:19:36 +0000141#if PJMEDIA_HAS_ILBC_CODEC
142 /* Register iLBC. */
143 status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt,
144 pjsua_var.media_cfg.ilbc_mode);
145 if (status != PJ_SUCCESS) {
146 pjsua_perror(THIS_FILE, "Error initializing iLBC codec",
147 status);
148 return status;
149 }
150#endif /* PJMEDIA_HAS_ILBC_CODEC */
151
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000152#if PJMEDIA_HAS_GSM_CODEC
153 /* Register GSM */
154 status = pjmedia_codec_gsm_init(pjsua_var.med_endpt);
155 if (status != PJ_SUCCESS) {
156 pjsua_perror(THIS_FILE, "Error initializing GSM codec",
157 status);
158 return status;
159 }
160#endif /* PJMEDIA_HAS_GSM_CODEC */
161
162#if PJMEDIA_HAS_G711_CODEC
163 /* Register PCMA and PCMU */
164 status = pjmedia_codec_g711_init(pjsua_var.med_endpt);
165 if (status != PJ_SUCCESS) {
166 pjsua_perror(THIS_FILE, "Error initializing G711 codec",
167 status);
168 return status;
169 }
170#endif /* PJMEDIA_HAS_G711_CODEC */
171
Benny Prijono7ffd7752008-03-17 14:07:53 +0000172#if PJMEDIA_HAS_G722_CODEC
173 status = pjmedia_codec_g722_init( pjsua_var.med_endpt );
174 if (status != PJ_SUCCESS) {
175 pjsua_perror(THIS_FILE, "Error initializing G722 codec",
176 status);
177 return status;
178 }
179#endif /* PJMEDIA_HAS_G722_CODEC */
180
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000181#if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000182 /* Register IPP codecs */
183 status = pjmedia_codec_ipp_init(pjsua_var.med_endpt);
184 if (status != PJ_SUCCESS) {
185 pjsua_perror(THIS_FILE, "Error initializing IPP codecs",
186 status);
187 return status;
188 }
189
Benny Prijonoa4e7cdd2008-08-19 15:01:48 +0000190#endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000191
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000192#if PJMEDIA_HAS_PASSTHROUGH_CODECS
193 /* Register passthrough codecs */
194 status = pjmedia_codec_passthrough_init(pjsua_var.med_endpt);
195 if (status != PJ_SUCCESS) {
196 pjsua_perror(THIS_FILE, "Error initializing passthrough codecs",
197 status);
198 return status;
199 }
200#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
201
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000202#if PJMEDIA_HAS_L16_CODEC
203 /* Register L16 family codecs, but disable all */
204 status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
205 if (status != PJ_SUCCESS) {
206 pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
207 status);
208 return status;
209 }
210
211 /* Disable ALL L16 codecs */
212 codec_id = pj_str("L16");
213 pjmedia_codec_mgr_set_codec_priority(
214 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
215 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
216
217#endif /* PJMEDIA_HAS_L16_CODEC */
218
219
220 /* Save additional conference bridge parameters for future
221 * reference.
222 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000223 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000224 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000225 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
226 pjsua_var.mconf_cfg.channel_count *
227 pjsua_var.media_cfg.audio_frame_ptime /
228 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000229
Benny Prijono0498d902006-06-19 14:49:14 +0000230 /* Init options for conference bridge. */
231 opt = PJMEDIA_CONF_NO_DEVICE;
232 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000233 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000234 {
235 opt |= PJMEDIA_CONF_SMALL_FILTER;
236 }
237 else if (pjsua_var.media_cfg.quality < 3) {
238 opt |= PJMEDIA_CONF_USE_LINEAR;
239 }
240
241
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000242 /* Init conference bridge. */
243 status = pjmedia_conf_create(pjsua_var.pool,
244 pjsua_var.media_cfg.max_media_ports,
245 pjsua_var.media_cfg.clock_rate,
246 pjsua_var.mconf_cfg.channel_count,
247 pjsua_var.mconf_cfg.samples_per_frame,
248 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000249 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000250 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000251 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000252 status);
253 return status;
254 }
255
Benny Prijonof798e502009-03-09 13:08:16 +0000256 /* Are we using the audio switchboard (a.k.a APS-Direct)? */
257 pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
258 ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
259
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000260 /* Create null port just in case user wants to use null sound. */
261 status = pjmedia_null_port_create(pjsua_var.pool,
262 pjsua_var.media_cfg.clock_rate,
263 pjsua_var.mconf_cfg.channel_count,
264 pjsua_var.mconf_cfg.samples_per_frame,
265 pjsua_var.mconf_cfg.bits_per_sample,
266 &pjsua_var.null_port);
267 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
268
Benny Prijono6ba8c542007-10-16 01:34:14 +0000269 /* Perform NAT detection */
270 pjsua_detect_nat_type();
271
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000272 return PJ_SUCCESS;
273}
274
275
276/*
277 * Create RTP and RTCP socket pair, and possibly resolve their public
278 * address via STUN.
279 */
280static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
281 pjmedia_sock_info *skinfo)
282{
283 enum {
284 RTP_RETRY = 100
285 };
286 int i;
Benny Prijono0a5cad82006-09-26 13:21:02 +0000287 pj_sockaddr_in bound_addr;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000288 pj_sockaddr_in mapped_addr[2];
289 pj_status_t status = PJ_SUCCESS;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000290 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000291 pj_sock_t sock[2];
292
Benny Prijonoc97608e2007-03-23 16:34:20 +0000293 /* Make sure STUN server resolution has completed */
294 status = pjsua_resolve_stun_server(PJ_TRUE);
295 if (status != PJ_SUCCESS) {
296 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
297 return status;
298 }
299
Benny Prijonode479562007-03-15 10:23:55 +0000300 if (next_rtp_port == 0)
301 next_rtp_port = (pj_uint16_t)cfg->port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000302
303 for (i=0; i<2; ++i)
304 sock[i] = PJ_INVALID_SOCKET;
305
Benny Prijono0a5cad82006-09-26 13:21:02 +0000306 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
307 if (cfg->bound_addr.slen) {
308 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
309 if (status != PJ_SUCCESS) {
310 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
311 status);
312 return status;
313 }
314 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000315
316 /* Loop retry to bind RTP and RTCP sockets. */
Benny Prijonode479562007-03-15 10:23:55 +0000317 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000318
319 /* Create and bind RTP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000320 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000321 if (status != PJ_SUCCESS) {
322 pjsua_perror(THIS_FILE, "socket() error", status);
323 return status;
324 }
325
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000326 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
327 next_rtp_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000328 if (status != PJ_SUCCESS) {
329 pj_sock_close(sock[0]);
330 sock[0] = PJ_INVALID_SOCKET;
331 continue;
332 }
333
334 /* Create and bind RTCP socket. */
Benny Prijono8ab968f2007-07-20 08:08:30 +0000335 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000336 if (status != PJ_SUCCESS) {
337 pjsua_perror(THIS_FILE, "socket() error", status);
338 pj_sock_close(sock[0]);
339 return status;
340 }
341
Benny Prijono0cdcd3f2007-12-09 14:14:11 +0000342 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
343 (pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000344 if (status != PJ_SUCCESS) {
345 pj_sock_close(sock[0]);
346 sock[0] = PJ_INVALID_SOCKET;
347
348 pj_sock_close(sock[1]);
349 sock[1] = PJ_INVALID_SOCKET;
350 continue;
351 }
352
353 /*
354 * If we're configured to use STUN, then find out the mapped address,
355 * and make sure that the mapped RTCP port is adjacent with the RTP.
356 */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000357 if (pjsua_var.stun_srv.addr.sa_family != 0) {
358 char ip_addr[32];
359 pj_str_t stun_srv;
360
361 pj_ansi_strcpy(ip_addr,
362 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
363 stun_srv = pj_str(ip_addr);
364
Benny Prijono14c2b862007-02-21 00:40:05 +0000365 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000366 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
367 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000368 mapped_addr);
369 if (status != PJ_SUCCESS) {
370 pjsua_perror(THIS_FILE, "STUN resolve error", status);
371 goto on_error;
372 }
373
Benny Prijono80eee892007-11-03 22:43:23 +0000374#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000375 if (pj_ntohs(mapped_addr[1].sin_port) ==
376 pj_ntohs(mapped_addr[0].sin_port)+1)
377 {
378 /* Success! */
379 break;
380 }
381
382 pj_sock_close(sock[0]);
383 sock[0] = PJ_INVALID_SOCKET;
384
385 pj_sock_close(sock[1]);
386 sock[1] = PJ_INVALID_SOCKET;
Benny Prijono80eee892007-11-03 22:43:23 +0000387#else
388 if (pj_ntohs(mapped_addr[1].sin_port) !=
389 pj_ntohs(mapped_addr[0].sin_port)+1)
390 {
391 PJ_LOG(4,(THIS_FILE,
392 "Note: STUN mapped RTCP port %d is not adjacent"
393 " to RTP port %d",
394 pj_ntohs(mapped_addr[1].sin_port),
395 pj_ntohs(mapped_addr[0].sin_port)));
396 }
397 /* Success! */
398 break;
399#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000400
Benny Prijono0a5cad82006-09-26 13:21:02 +0000401 } else if (cfg->public_addr.slen) {
402
403 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000404 (pj_uint16_t)next_rtp_port);
Benny Prijono0a5cad82006-09-26 13:21:02 +0000405 if (status != PJ_SUCCESS)
406 goto on_error;
407
408 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
Benny Prijonode479562007-03-15 10:23:55 +0000409 (pj_uint16_t)(next_rtp_port+1));
Benny Prijono0a5cad82006-09-26 13:21:02 +0000410 if (status != PJ_SUCCESS)
411 goto on_error;
412
413 break;
414
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000415 } else {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000416
Benny Prijono42d08d22007-12-20 11:23:07 +0000417 if (bound_addr.sin_addr.s_addr == 0) {
418 pj_sockaddr addr;
419
420 /* Get local IP address. */
421 status = pj_gethostip(pj_AF_INET(), &addr);
422 if (status != PJ_SUCCESS)
423 goto on_error;
424
425 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
426 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000427
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000428 for (i=0; i<2; ++i) {
429 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
Benny Prijono42d08d22007-12-20 11:23:07 +0000430 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
Benny Prijonofe0b1d62007-12-05 04:07:37 +0000431 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000432
Benny Prijonode479562007-03-15 10:23:55 +0000433 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
434 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000435 break;
436 }
437 }
438
439 if (sock[0] == PJ_INVALID_SOCKET) {
440 PJ_LOG(1,(THIS_FILE,
441 "Unable to find appropriate RTP/RTCP ports combination"));
442 goto on_error;
443 }
444
445
446 skinfo->rtp_sock = sock[0];
447 pj_memcpy(&skinfo->rtp_addr_name,
448 &mapped_addr[0], sizeof(pj_sockaddr_in));
449
450 skinfo->rtcp_sock = sock[1];
451 pj_memcpy(&skinfo->rtcp_addr_name,
452 &mapped_addr[1], sizeof(pj_sockaddr_in));
453
Benny Prijono8b22ce12008-02-08 12:57:55 +0000454 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000455 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
456 sizeof(addr_buf), 3)));
Benny Prijono8b22ce12008-02-08 12:57:55 +0000457 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
Benny Prijono5186eae2007-12-03 14:38:25 +0000458 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
459 sizeof(addr_buf), 3)));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000460
Benny Prijonode479562007-03-15 10:23:55 +0000461 next_rtp_port += 2;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000462 return PJ_SUCCESS;
463
464on_error:
465 for (i=0; i<2; ++i) {
466 if (sock[i] != PJ_INVALID_SOCKET)
467 pj_sock_close(sock[i]);
468 }
469 return status;
470}
471
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000472/* Check if sound device is idle. */
473static void check_snd_dev_idle()
474{
475
476 /* Activate sound device auto-close timer if sound device is idle.
477 * It is idle when there is no port connection in the bridge.
478 */
479 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL) &&
480 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
481 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
482 pjsua_var.media_cfg.snd_auto_close_time >= 0)
483 {
484 pj_time_val delay;
485
486 delay.msec = 0;
487 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
488
489 pjsua_var.snd_idle_timer.id = PJ_TRUE;
490 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
491 &delay);
492 }
493}
494
495
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000496/* Timer callback to close sound device */
497static void close_snd_timer_cb( pj_timer_heap_t *th,
498 pj_timer_entry *entry)
499{
500 PJ_UNUSED_ARG(th);
501
502 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
503 pjsua_var.media_cfg.snd_auto_close_time));
504
505 entry->id = PJ_FALSE;
506
507 close_snd_dev();
508}
509
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000510
511/*
512 * Start pjsua media subsystem.
513 */
514pj_status_t pjsua_media_subsys_start(void)
515{
516 pj_status_t status;
517
518 /* Create media for calls, if none is specified */
519 if (pjsua_var.calls[0].med_tp == NULL) {
520 pjsua_transport_config transport_cfg;
521
522 /* Create default transport config */
523 pjsua_transport_config_default(&transport_cfg);
524 transport_cfg.port = DEFAULT_RTP_PORT;
525
526 status = pjsua_media_transports_create(&transport_cfg);
527 if (status != PJ_SUCCESS)
528 return status;
529 }
530
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000531 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
532 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000533
534 return PJ_SUCCESS;
535}
536
537
538/*
539 * Destroy pjsua media subsystem.
540 */
541pj_status_t pjsua_media_subsys_destroy(void)
542{
543 unsigned i;
544
545 close_snd_dev();
546
547 if (pjsua_var.mconf) {
548 pjmedia_conf_destroy(pjsua_var.mconf);
549 pjsua_var.mconf = NULL;
550 }
551
552 if (pjsua_var.null_port) {
553 pjmedia_port_destroy(pjsua_var.null_port);
554 pjsua_var.null_port = NULL;
555 }
556
557 /* Destroy file players */
558 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
559 if (pjsua_var.player[i].port) {
560 pjmedia_port_destroy(pjsua_var.player[i].port);
561 pjsua_var.player[i].port = NULL;
562 }
563 }
564
565 /* Destroy file recorders */
566 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
567 if (pjsua_var.recorder[i].port) {
568 pjmedia_port_destroy(pjsua_var.recorder[i].port);
569 pjsua_var.recorder[i].port = NULL;
570 }
571 }
572
573 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000574 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono311b63f2008-07-14 11:31:40 +0000575 if (pjsua_var.calls[i].med_tp_st != PJSUA_MED_TP_IDLE) {
576 pjsua_media_channel_deinit(i);
577 }
Benny Prijono40860c32008-09-04 13:55:33 +0000578 if (pjsua_var.calls[i].med_tp && pjsua_var.calls[i].med_tp_auto_del) {
579 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000580 }
Benny Prijono40860c32008-09-04 13:55:33 +0000581 pjsua_var.calls[i].med_tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000582 }
583
584 /* Destroy media endpoint. */
585 if (pjsua_var.med_endpt) {
586
587 /* Shutdown all codecs: */
588# if PJMEDIA_HAS_SPEEX_CODEC
589 pjmedia_codec_speex_deinit();
590# endif /* PJMEDIA_HAS_SPEEX_CODEC */
591
592# if PJMEDIA_HAS_GSM_CODEC
593 pjmedia_codec_gsm_deinit();
594# endif /* PJMEDIA_HAS_GSM_CODEC */
595
596# if PJMEDIA_HAS_G711_CODEC
597 pjmedia_codec_g711_deinit();
598# endif /* PJMEDIA_HAS_G711_CODEC */
599
Benny Prijono7ffd7752008-03-17 14:07:53 +0000600# if PJMEDIA_HAS_G722_CODEC
601 pjmedia_codec_g722_deinit();
602# endif /* PJMEDIA_HAS_G722_CODEC */
603
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000604# if PJMEDIA_HAS_INTEL_IPP
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000605 pjmedia_codec_ipp_deinit();
Nanang Izzuddin6df1d532008-08-25 13:46:03 +0000606# endif /* PJMEDIA_HAS_INTEL_IPP */
Nanang Izzuddin7dd32682008-08-19 11:23:33 +0000607
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000608# if PJMEDIA_HAS_PASSTHROUGH_CODECS
609 pjmedia_codec_passthrough_deinit();
610# endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
611
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000612# if PJMEDIA_HAS_L16_CODEC
613 pjmedia_codec_l16_deinit();
614# endif /* PJMEDIA_HAS_L16_CODEC */
615
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000616 pjmedia_endpt_destroy(pjsua_var.med_endpt);
617 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000618
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000619 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000620 // Not necessary, as pjmedia_snd_deinit() should have been called
621 // in pjmedia_endpt_destroy().
622 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000623 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000624
Benny Prijonode479562007-03-15 10:23:55 +0000625 /* Reset RTP port */
626 next_rtp_port = 0;
627
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000628 return PJ_SUCCESS;
629}
630
631
Benny Prijonoc97608e2007-03-23 16:34:20 +0000632/* Create normal UDP media transports */
633static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000634{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000635 unsigned i;
Benny Prijono617c5bc2007-04-02 19:51:21 +0000636 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000637 pj_status_t status;
638
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000639 /* Create each media transport */
640 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
641
Benny Prijono617c5bc2007-04-02 19:51:21 +0000642 status = create_rtp_rtcp_sock(cfg, &skinfo);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000643 if (status != PJ_SUCCESS) {
644 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
645 status);
646 goto on_error;
647 }
Benny Prijonod8179652008-01-23 20:39:07 +0000648
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000649 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
Benny Prijono617c5bc2007-04-02 19:51:21 +0000650 &skinfo, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000651 &pjsua_var.calls[i].med_tp);
652 if (status != PJ_SUCCESS) {
653 pjsua_perror(THIS_FILE, "Unable to create media transport",
654 status);
655 goto on_error;
656 }
Benny Prijono00cae612006-07-31 15:19:36 +0000657
Benny Prijonod8179652008-01-23 20:39:07 +0000658 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
659 PJMEDIA_DIR_ENCODING,
660 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000661
Benny Prijonod8179652008-01-23 20:39:07 +0000662 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
663 PJMEDIA_DIR_DECODING,
664 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijono00cae612006-07-31 15:19:36 +0000665
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000666 }
667
Benny Prijonoc97608e2007-03-23 16:34:20 +0000668 return PJ_SUCCESS;
669
670on_error:
671 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
672 if (pjsua_var.calls[i].med_tp != NULL) {
673 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
674 pjsua_var.calls[i].med_tp = NULL;
675 }
676 }
677
678 return status;
679}
680
681
Benny Prijono096c56c2007-09-15 08:30:16 +0000682/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000683static void on_ice_complete(pjmedia_transport *tp,
684 pj_ice_strans_op op,
685 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000686{
Benny Prijonof76e1392008-06-06 14:51:48 +0000687 unsigned id;
Benny Prijono096c56c2007-09-15 08:30:16 +0000688 pj_bool_t found = PJ_FALSE;
689
Benny Prijono096c56c2007-09-15 08:30:16 +0000690 /* Find call which has this media transport */
691
692 PJSUA_LOCK();
693
Benny Prijonof76e1392008-06-06 14:51:48 +0000694 for (id=0; id<pjsua_var.ua_cfg.max_calls; ++id) {
695 if (pjsua_var.calls[id].med_tp == tp ||
696 pjsua_var.calls[id].med_orig == tp)
697 {
698 found = PJ_TRUE;
699 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000700 }
701 }
702
703 PJSUA_UNLOCK();
704
Benny Prijonof76e1392008-06-06 14:51:48 +0000705 if (!found)
706 return;
707
708 switch (op) {
709 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono224b4e22008-06-19 14:10:28 +0000710 pjsua_var.calls[id].med_tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000711 break;
712 case PJ_ICE_STRANS_OP_NEGOTIATION:
713 if (result != PJ_SUCCESS) {
714 pjsua_var.calls[id].media_st = PJSUA_CALL_MEDIA_ERROR;
715 pjsua_var.calls[id].media_dir = PJMEDIA_DIR_NONE;
716
717 if (pjsua_var.ua_cfg.cb.on_call_media_state) {
718 pjsua_var.ua_cfg.cb.on_call_media_state(id);
719 }
720 }
721 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000722 }
723}
724
725
Benny Prijonof76e1392008-06-06 14:51:48 +0000726/* Parse "HOST:PORT" format */
727static pj_status_t parse_host_port(const pj_str_t *host_port,
728 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000729{
Benny Prijonof76e1392008-06-06 14:51:48 +0000730 pj_str_t str_port;
731
732 str_port.ptr = pj_strchr(host_port, ':');
733 if (str_port.ptr != NULL) {
734 int iport;
735
736 host->ptr = host_port->ptr;
737 host->slen = (str_port.ptr - host->ptr);
738 str_port.ptr++;
739 str_port.slen = host_port->slen - host->slen - 1;
740 iport = (int)pj_strtoul(&str_port);
741 if (iport < 1 || iport > 65535)
742 return PJ_EINVAL;
743 *port = (pj_uint16_t)iport;
744 } else {
745 *host = *host_port;
746 *port = 0;
747 }
748
749 return PJ_SUCCESS;
750}
751
752/* Create ICE media transports (when ice is enabled) */
753static pj_status_t create_ice_media_transports(void)
754{
755 char stunip[PJ_INET6_ADDRSTRLEN];
756 pj_ice_strans_cfg ice_cfg;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000757 unsigned i;
758 pj_status_t status;
759
Benny Prijonoda9785b2007-04-02 20:43:06 +0000760 /* Make sure STUN server resolution has completed */
761 status = pjsua_resolve_stun_server(PJ_TRUE);
762 if (status != PJ_SUCCESS) {
763 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
764 return status;
765 }
766
Benny Prijonof76e1392008-06-06 14:51:48 +0000767 /* Create ICE stream transport configuration */
768 pj_ice_strans_cfg_default(&ice_cfg);
769 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
770 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
771 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
772
773 ice_cfg.af = pj_AF_INET();
774 ice_cfg.resolver = pjsua_var.resolver;
775
776 /* Configure STUN settings */
777 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
778 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
779 ice_cfg.stun.server = pj_str(stunip);
780 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
781 }
782 ice_cfg.stun.no_host_cands = pjsua_var.media_cfg.ice_no_host_cands;
783
784 /* Configure TURN settings */
785 if (pjsua_var.media_cfg.enable_turn) {
786 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
787 &ice_cfg.turn.server,
788 &ice_cfg.turn.port);
789 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
790 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
791 return PJ_EINVAL;
792 }
793 if (ice_cfg.turn.port == 0)
794 ice_cfg.turn.port = 3479;
795 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
796 pj_memcpy(&ice_cfg.turn.auth_cred,
797 &pjsua_var.media_cfg.turn_auth_cred,
798 sizeof(ice_cfg.turn.auth_cred));
799 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000800
Benny Prijonoc97608e2007-03-23 16:34:20 +0000801 /* Create each media transport */
802 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono096c56c2007-09-15 08:30:16 +0000803 pjmedia_ice_cb ice_cb;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000804 char name[32];
Benny Prijonof76e1392008-06-06 14:51:48 +0000805 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000806
Benny Prijono096c56c2007-09-15 08:30:16 +0000807 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
808 ice_cb.on_ice_complete = &on_ice_complete;
Benny Prijono38fb3ea2008-01-02 08:27:03 +0000809 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", i);
Benny Prijono224b4e22008-06-19 14:10:28 +0000810 pjsua_var.calls[i].med_tp_ready = PJ_EPENDING;
Benny Prijonof76e1392008-06-06 14:51:48 +0000811
812 comp_cnt = 1;
Benny Prijono551af422008-08-07 09:55:52 +0000813 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
Benny Prijonof76e1392008-06-06 14:51:48 +0000814 ++comp_cnt;
815
816 status = pjmedia_ice_create(pjsua_var.med_endpt, name, comp_cnt,
817 &ice_cfg, &ice_cb,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000818 &pjsua_var.calls[i].med_tp);
819 if (status != PJ_SUCCESS) {
820 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
821 status);
822 goto on_error;
823 }
824
Benny Prijonof76e1392008-06-06 14:51:48 +0000825 /* Wait until transport is initialized, or time out */
826 PJSUA_UNLOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000827 while (pjsua_var.calls[i].med_tp_ready == PJ_EPENDING) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000828 pjsua_handle_events(100);
829 }
830 PJSUA_LOCK();
Benny Prijono224b4e22008-06-19 14:10:28 +0000831 if (pjsua_var.calls[i].med_tp_ready != PJ_SUCCESS) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000832 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
Benny Prijono224b4e22008-06-19 14:10:28 +0000833 pjsua_var.calls[i].med_tp_ready);
834 status = pjsua_var.calls[i].med_tp_ready;
Benny Prijonof76e1392008-06-06 14:51:48 +0000835 goto on_error;
836 }
837
Benny Prijonod8179652008-01-23 20:39:07 +0000838 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
839 PJMEDIA_DIR_ENCODING,
840 pjsua_var.media_cfg.tx_drop_pct);
Benny Prijono11da9bc2007-09-15 08:55:00 +0000841
Benny Prijonod8179652008-01-23 20:39:07 +0000842 pjmedia_transport_simulate_lost(pjsua_var.calls[i].med_tp,
843 PJMEDIA_DIR_DECODING,
844 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000845 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000846
847 return PJ_SUCCESS;
848
849on_error:
850 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
851 if (pjsua_var.calls[i].med_tp != NULL) {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000852 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000853 pjsua_var.calls[i].med_tp = NULL;
854 }
855 }
856
Benny Prijonoc97608e2007-03-23 16:34:20 +0000857 return status;
858}
859
860
861/*
862 * Create UDP media transports for all the calls. This function creates
863 * one UDP media transport for each call.
864 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +0000865PJ_DEF(pj_status_t) pjsua_media_transports_create(
866 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000867{
868 pjsua_transport_config cfg;
869 unsigned i;
870 pj_status_t status;
871
872
873 /* Make sure pjsua_init() has been called */
874 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
875
876 PJSUA_LOCK();
877
878 /* Delete existing media transports */
879 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono40860c32008-09-04 13:55:33 +0000880 if (pjsua_var.calls[i].med_tp != NULL &&
881 pjsua_var.calls[i].med_tp_auto_del)
882 {
Benny Prijonoc97608e2007-03-23 16:34:20 +0000883 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
884 pjsua_var.calls[i].med_tp = NULL;
Nanang Izzuddind704a8b2008-09-23 16:34:07 +0000885 pjsua_var.calls[i].med_orig = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000886 }
887 }
888
889 /* Copy config */
890 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
891
Benny Prijono40860c32008-09-04 13:55:33 +0000892 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +0000893 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijonof76e1392008-06-06 14:51:48 +0000894 status = create_ice_media_transports();
Benny Prijonoc97608e2007-03-23 16:34:20 +0000895 } else {
896 status = create_udp_media_transports(&cfg);
897 }
898
Benny Prijono40860c32008-09-04 13:55:33 +0000899 /* Set media transport auto_delete to True */
900 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
901 pjsua_var.calls[i].med_tp_auto_del = PJ_TRUE;
902 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000903
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000904 PJSUA_UNLOCK();
905
906 return status;
907}
908
Benny Prijono40860c32008-09-04 13:55:33 +0000909/*
910 * Attach application's created media transports.
911 */
912PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
913 unsigned count,
914 pj_bool_t auto_delete)
915{
916 unsigned i;
917
918 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
919
920 /* Assign the media transports */
921 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
922 if (pjsua_var.calls[i].med_tp != NULL &&
923 pjsua_var.calls[i].med_tp_auto_del)
924 {
925 pjmedia_transport_close(pjsua_var.calls[i].med_tp);
926 }
927
928 pjsua_var.calls[i].med_tp = tp[i].transport;
929 pjsua_var.calls[i].med_tp_auto_del = auto_delete;
930 }
931
932 return PJ_SUCCESS;
933}
934
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000935
Benny Prijonoc97608e2007-03-23 16:34:20 +0000936pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
Benny Prijonod8179652008-01-23 20:39:07 +0000937 pjsip_role_e role,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000938 int security_level,
Benny Prijono224b4e22008-06-19 14:10:28 +0000939 pj_pool_t *tmp_pool,
940 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +0000941 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000942{
943 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono224b4e22008-06-19 14:10:28 +0000944 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000945
Benny Prijonod8179652008-01-23 20:39:07 +0000946#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
947 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
948 pjmedia_srtp_setting srtp_opt;
Benny Prijonoa310bd22008-06-27 21:19:44 +0000949 pjmedia_transport *srtp = NULL;
Benny Prijonod8179652008-01-23 20:39:07 +0000950#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000951
Benny Prijonod8179652008-01-23 20:39:07 +0000952 PJ_UNUSED_ARG(role);
Benny Prijonoc97608e2007-03-23 16:34:20 +0000953
Benny Prijonod8179652008-01-23 20:39:07 +0000954 /* Return error if media transport has not been created yet
955 * (e.g. application is starting)
956 */
957 if (call->med_tp == NULL) {
Benny Prijono03789052008-09-16 14:30:50 +0000958 if (sip_err_code)
959 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
Benny Prijonod8179652008-01-23 20:39:07 +0000960 return PJ_EBUSY;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000961 }
962
Benny Prijonod8179652008-01-23 20:39:07 +0000963#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Benny Prijono53a7c702008-04-14 02:57:29 +0000964 /* This function may be called when SRTP transport already exists
965 * (e.g: in re-invite, update), don't need to destroy/re-create.
966 */
967 if (!call->med_orig || call->med_tp == call->med_orig) {
968
969 /* Check if SRTP requires secure signaling */
970 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
971 if (security_level < acc->cfg.srtp_secure_signaling) {
972 if (sip_err_code)
973 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
974 return PJSIP_ESESSIONINSECURE;
975 }
Benny Prijonod8179652008-01-23 20:39:07 +0000976 }
Benny Prijonod8179652008-01-23 20:39:07 +0000977
Benny Prijono53a7c702008-04-14 02:57:29 +0000978 /* Always create SRTP adapter */
979 pjmedia_srtp_setting_default(&srtp_opt);
980 srtp_opt.close_member_tp = PJ_FALSE;
Nanang Izzuddin4375f902008-06-26 19:12:09 +0000981 /* If media session has been ever established, let's use remote's
982 * preference in SRTP usage policy, especially when it is stricter.
983 */
984 if (call->rem_srtp_use > acc->cfg.use_srtp)
985 srtp_opt.use = call->rem_srtp_use;
986 else
987 srtp_opt.use = acc->cfg.use_srtp;
988
Benny Prijono53a7c702008-04-14 02:57:29 +0000989 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
990 call->med_tp,
991 &srtp_opt, &srtp);
992 if (status != PJ_SUCCESS) {
993 if (sip_err_code)
994 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
995 return status;
996 }
Benny Prijonod8179652008-01-23 20:39:07 +0000997
Benny Prijono53a7c702008-04-14 02:57:29 +0000998 /* Set SRTP as current media transport */
999 call->med_orig = call->med_tp;
1000 call->med_tp = srtp;
1001 }
Benny Prijonod8179652008-01-23 20:39:07 +00001002#else
1003 call->med_orig = call->med_tp;
1004 PJ_UNUSED_ARG(security_level);
1005#endif
1006
Benny Prijonoa310bd22008-06-27 21:19:44 +00001007 /* Find out which media line in SDP that we support. If we are offerer,
1008 * audio will be at index 0 in SDP.
1009 */
1010 if (rem_sdp == 0) {
1011 call->audio_idx = 0;
1012 }
1013 /* Otherwise find out the candidate audio media line in SDP */
1014 else {
1015 unsigned i;
1016 pj_bool_t srtp_active;
1017
1018#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1019 srtp_active = acc->cfg.use_srtp && srtp != NULL;
1020#else
1021 srtp_active = PJ_FALSE;
1022#endif
1023
1024 /* Media count must have been checked */
1025 pj_assert(rem_sdp->media_count != 0);
1026
1027 for (i=0; i<rem_sdp->media_count; ++i) {
1028 const pjmedia_sdp_media *m = rem_sdp->media[i];
1029
1030 /* Skip if media is not audio */
1031 if (pj_stricmp2(&m->desc.media, "audio") != 0)
1032 continue;
1033
1034 /* Skip if media is disabled */
1035 if (m->desc.port == 0)
1036 continue;
1037
1038 /* Skip if transport is not supported */
1039 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") != 0 &&
1040 pj_stricmp2(&m->desc.transport, "RTP/SAVP") != 0)
1041 {
1042 continue;
1043 }
1044
1045 if (call->audio_idx == -1) {
1046 call->audio_idx = i;
1047 } else {
1048 /* We've found multiple candidates. This could happen
1049 * e.g. when remote is offering both RTP/AVP and RTP/AVP,
1050 * or when remote for some reason offers two audio.
1051 */
1052
1053 if (srtp_active &&
1054 pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0)
1055 {
1056 /* Prefer RTP/SAVP when our media transport is SRTP */
1057 call->audio_idx = i;
1058 } else if (!srtp_active &&
1059 pj_stricmp2(&m->desc.transport, "RTP/AVP")==0)
1060 {
1061 /* Prefer RTP/AVP when our media transport is NOT SRTP */
1062 call->audio_idx = i;
1063 }
1064 }
1065 }
1066 }
1067
1068 /* Reject offer if we couldn't find a good m=audio line in offer */
1069 if (call->audio_idx < 0) {
Benny Prijonoab8dba92008-06-27 21:59:15 +00001070 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001071 pjsua_media_channel_deinit(call_id);
Benny Prijonoab8dba92008-06-27 21:59:15 +00001072 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001073 }
1074
1075 PJ_LOG(4,(THIS_FILE, "Media index %d selected for call %d",
1076 call->audio_idx, call->index));
1077
Benny Prijono224b4e22008-06-19 14:10:28 +00001078 /* Create the media transport */
1079 status = pjmedia_transport_media_create(call->med_tp, tmp_pool, 0,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001080 rem_sdp, call->audio_idx);
Benny Prijono224b4e22008-06-19 14:10:28 +00001081 if (status != PJ_SUCCESS) {
1082 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1083 pjsua_media_channel_deinit(call_id);
1084 return status;
1085 }
1086
1087 call->med_tp_st = PJSUA_MED_TP_INIT;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001088 return PJ_SUCCESS;
1089}
1090
1091pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1092 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001093 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001094 pjmedia_sdp_session **p_sdp,
1095 int *sip_status_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001096{
Benny Prijonoa310bd22008-06-27 21:19:44 +00001097 enum { MAX_MEDIA = 1 };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001098 pjmedia_sdp_session *sdp;
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001099 pjmedia_transport_info tpinfo;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001100 pjsua_call *call = &pjsua_var.calls[call_id];
1101 pj_status_t status;
1102
Benny Prijono55e82352007-05-10 20:49:08 +00001103 /* Return error if media transport has not been created yet
1104 * (e.g. application is starting)
1105 */
1106 if (call->med_tp == NULL) {
1107 return PJ_EBUSY;
1108 }
1109
Benny Prijonoa310bd22008-06-27 21:19:44 +00001110 /* Media index must have been determined before */
1111 pj_assert(call->audio_idx != -1);
1112
Benny Prijono224b4e22008-06-19 14:10:28 +00001113 /* Create media if it's not created. This could happen when call is
1114 * currently on-hold
1115 */
1116 if (call->med_tp_st == PJSUA_MED_TP_IDLE) {
1117 pjsip_role_e role;
1118 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1119 status = pjsua_media_channel_init(call_id, role, call->secure_level,
1120 pool, rem_sdp, sip_status_code);
1121 if (status != PJ_SUCCESS)
1122 return status;
1123 }
1124
Benny Prijono617c5bc2007-04-02 19:51:21 +00001125 /* Get media socket info */
Benny Prijono734fc2d2008-03-17 16:05:35 +00001126 pjmedia_transport_info_init(&tpinfo);
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001127 pjmedia_transport_get_info(call->med_tp, &tpinfo);
Benny Prijono617c5bc2007-04-02 19:51:21 +00001128
1129 /* Create SDP */
Benny Prijonod8179652008-01-23 20:39:07 +00001130 status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, MAX_MEDIA,
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001131 &tpinfo.sock_info, &sdp);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001132 if (status != PJ_SUCCESS) {
1133 if (sip_status_code) *sip_status_code = 500;
Benny Prijono224b4e22008-06-19 14:10:28 +00001134 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001135 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001136
Benny Prijonoa310bd22008-06-27 21:19:44 +00001137 /* If we're answering and the selected media is not the first media
1138 * in SDP, then fill in the unselected media with with zero port.
1139 * Otherwise we'll crash in transport_encode_sdp() because the media
1140 * lines are not aligned between offer and answer.
1141 */
1142 if (rem_sdp && call->audio_idx != 0) {
1143 unsigned i;
1144
1145 for (i=0; i<rem_sdp->media_count; ++i) {
1146 const pjmedia_sdp_media *rem_m = rem_sdp->media[i];
1147 pjmedia_sdp_media *m;
1148 const pjmedia_sdp_attr *a;
1149
1150 if ((int)i == call->audio_idx)
1151 continue;
1152
1153 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1154 pj_strdup(pool, &m->desc.media, &rem_m->desc.media);
1155 pj_strdup(pool, &m->desc.transport, &rem_m->desc.transport);
1156 m->desc.port = 0;
1157
1158 /* Add one format, copy from the offer. And copy the corresponding
1159 * rtpmap and fmtp attributes too.
1160 */
1161 m->desc.fmt_count = 1;
1162 pj_strdup(pool, &m->desc.fmt[0], &rem_m->desc.fmt[0]);
1163 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1164 "rtpmap", &m->desc.fmt[0])) != NULL)
1165 {
1166 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1167 }
1168 if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr,
1169 "fmtp", &m->desc.fmt[0])) != NULL)
1170 {
1171 m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a);
1172 }
1173
1174 if (i==sdp->media_count)
1175 sdp->media[sdp->media_count++] = m;
1176 else {
1177 pj_array_insert(sdp->media, sizeof(sdp->media[0]),
1178 sdp->media_count, i, &m);
1179 ++sdp->media_count;
1180 }
1181 }
1182 }
1183
Benny Prijono6ba8c542007-10-16 01:34:14 +00001184 /* Add NAT info in the SDP */
1185 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1186 pjmedia_sdp_attr *a;
1187 pj_str_t value;
1188 char nat_info[80];
1189
1190 value.ptr = nat_info;
1191 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1192 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1193 "%d", pjsua_var.nat_type);
1194 } else {
1195 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1196 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1197 "%d %s",
1198 pjsua_var.nat_type,
1199 type_name);
1200 }
1201
1202 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1203
1204 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1205
1206 }
1207
Benny Prijonod8179652008-01-23 20:39:07 +00001208 /* Give the SDP to media transport */
Benny Prijono224b4e22008-06-19 14:10:28 +00001209 status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp,
Benny Prijonoa310bd22008-06-27 21:19:44 +00001210 call->audio_idx);
Benny Prijono25b2ea12008-01-24 19:20:54 +00001211 if (status != PJ_SUCCESS) {
1212 if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono224b4e22008-06-19 14:10:28 +00001213 return status;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001214 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001215
1216 *p_sdp = sdp;
1217 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001218}
1219
1220
1221static void stop_media_session(pjsua_call_id call_id)
1222{
1223 pjsua_call *call = &pjsua_var.calls[call_id];
1224
1225 if (call->conf_slot != PJSUA_INVALID_ID) {
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001226 if (pjsua_var.mconf) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001227 pjsua_conf_remove_port(call->conf_slot);
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001228 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001229 call->conf_slot = PJSUA_INVALID_ID;
1230 }
1231
1232 if (call->session) {
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001233 pjmedia_rtcp_stat stat;
1234
Nanang Izzuddin437d77c2008-08-26 18:04:15 +00001235 if (pjmedia_session_get_stream_stat(call->session, 0, &stat)
1236 == PJ_SUCCESS)
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001237 {
1238 /* Save RTP timestamp & sequence, so when media session is
1239 * restarted, those values will be restored as the initial
1240 * RTP timestamp & sequence of the new media session. So in
1241 * the same call session, RTP timestamp and sequence are
1242 * guaranteed to be contigue.
1243 */
1244 call->rtp_tx_seq_ts_set = 1 | (1 << 1);
1245 call->rtp_tx_seq = stat.rtp_tx_last_seq;
1246 call->rtp_tx_ts = stat.rtp_tx_last_ts;
1247 }
1248
Benny Prijonofc13bf62008-02-20 08:56:15 +00001249 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1250 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0);
1251 }
1252
Benny Prijonoc97608e2007-03-23 16:34:20 +00001253 pjmedia_session_destroy(call->session);
1254 call->session = NULL;
1255
1256 PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed",
1257 call_id));
1258
1259 }
1260
1261 call->media_st = PJSUA_CALL_MEDIA_NONE;
1262}
1263
1264pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1265{
1266 pjsua_call *call = &pjsua_var.calls[call_id];
1267
1268 stop_media_session(call_id);
1269
Benny Prijono224b4e22008-06-19 14:10:28 +00001270 if (call->med_tp_st != PJSUA_MED_TP_IDLE) {
1271 pjmedia_transport_media_stop(call->med_tp);
1272 call->med_tp_st = PJSUA_MED_TP_IDLE;
1273 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001274
Benny Prijono311b63f2008-07-14 11:31:40 +00001275 if (call->med_orig && call->med_tp && call->med_tp != call->med_orig) {
Benny Prijonod8179652008-01-23 20:39:07 +00001276 pjmedia_transport_close(call->med_tp);
1277 call->med_tp = call->med_orig;
1278 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00001279
1280 check_snd_dev_idle();
1281
Benny Prijonoc97608e2007-03-23 16:34:20 +00001282 return PJ_SUCCESS;
1283}
1284
1285
1286/*
1287 * DTMF callback from the stream.
1288 */
1289static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1290 int digit)
1291{
1292 PJ_UNUSED_ARG(strm);
1293
Benny Prijono0c068262008-02-14 14:38:52 +00001294 /* For discussions about call mutex protection related to this
1295 * callback, please see ticket #460:
1296 * http://trac.pjsip.org/repos/ticket/460#comment:4
1297 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001298 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1299 pjsua_call_id call_id;
1300
Benny Prijonod8179652008-01-23 20:39:07 +00001301 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001302 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1303 }
1304}
1305
1306
1307pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
Benny Prijono224b4e22008-06-19 14:10:28 +00001308 const pjmedia_sdp_session *local_sdp,
Benny Prijonodbce2cf2007-03-28 16:24:00 +00001309 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001310{
1311 int prev_media_st = 0;
1312 pjsua_call *call = &pjsua_var.calls[call_id];
1313 pjmedia_session_info sess_info;
Benny Prijono91e567e2007-12-28 08:51:58 +00001314 pjmedia_stream_info *si = NULL;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001315 pjmedia_port *media_port;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001316 pj_status_t status;
1317
1318 /* Destroy existing media session, if any. */
1319 prev_media_st = call->media_st;
1320 stop_media_session(call->index);
1321
1322 /* Create media session info based on SDP parameters.
Benny Prijonoc97608e2007-03-23 16:34:20 +00001323 */
1324 status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
1325 pjsua_var.med_endpt,
Benny Prijono91e567e2007-12-28 08:51:58 +00001326 PJMEDIA_MAX_SDP_MEDIA, &sess_info,
Benny Prijonoc97608e2007-03-23 16:34:20 +00001327 local_sdp, remote_sdp);
1328 if (status != PJ_SUCCESS)
1329 return status;
1330
Benny Prijonoa310bd22008-06-27 21:19:44 +00001331 /* Find which session is audio */
1332 PJ_ASSERT_RETURN(call->audio_idx != -1, PJ_EBUG);
1333 PJ_ASSERT_RETURN(call->audio_idx < (int)sess_info.stream_cnt, PJ_EBUG);
1334 si = &sess_info.stream_info[call->audio_idx];
Benny Prijono91e567e2007-12-28 08:51:58 +00001335
1336 /* Reset session info with only one media stream */
1337 sess_info.stream_cnt = 1;
1338 if (si != &sess_info.stream_info[0])
1339 pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001340
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001341 /* Check if no media is active */
Benny Prijono91e567e2007-12-28 08:51:58 +00001342 if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001343 {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001344 /* Call media state */
1345 call->media_st = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001346
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001347 /* Call media direction */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001348 call->media_dir = PJMEDIA_DIR_NONE;
1349
Benny Prijonod8179652008-01-23 20:39:07 +00001350 /* Shutdown transport's session */
1351 pjmedia_transport_media_stop(call->med_tp);
Benny Prijono224b4e22008-06-19 14:10:28 +00001352 call->med_tp_st = PJSUA_MED_TP_IDLE;
Benny Prijono667952e2007-04-02 19:27:54 +00001353
Benny Prijonoc97608e2007-03-23 16:34:20 +00001354 /* No need because we need keepalive? */
1355
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001356 /* Close upper entry of transport stack */
1357 if (call->med_orig && (call->med_tp != call->med_orig)) {
1358 pjmedia_transport_close(call->med_tp);
1359 call->med_tp = call->med_orig;
1360 }
1361
Benny Prijonoc97608e2007-03-23 16:34:20 +00001362 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001363 pjmedia_transport_info tp_info;
1364
Benny Prijono224b4e22008-06-19 14:10:28 +00001365 /* Start/restart media transport */
Benny Prijonod8179652008-01-23 20:39:07 +00001366 status = pjmedia_transport_media_start(call->med_tp,
1367 call->inv->pool,
1368 local_sdp, remote_sdp, 0);
1369 if (status != PJ_SUCCESS)
1370 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001371
Benny Prijono224b4e22008-06-19 14:10:28 +00001372 call->med_tp_st = PJSUA_MED_TP_RUNNING;
1373
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001374 /* Get remote SRTP usage policy */
1375 pjmedia_transport_info_init(&tp_info);
1376 pjmedia_transport_get_info(call->med_tp, &tp_info);
1377 if (tp_info.specific_info_cnt > 0) {
1378 int i;
1379 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1380 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1381 {
1382 pjmedia_srtp_info *srtp_info =
1383 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1384
1385 call->rem_srtp_use = srtp_info->peer_use;
1386 break;
1387 }
1388 }
1389 }
1390
Benny Prijonoc97608e2007-03-23 16:34:20 +00001391 /* Override ptime, if this option is specified. */
1392 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001393 si->param->setting.frm_per_pkt = (pj_uint8_t)
1394 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1395 if (si->param->setting.frm_per_pkt == 0)
1396 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001397 }
1398
1399 /* Disable VAD, if this option is specified. */
1400 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001401 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001402 }
1403
1404
1405 /* Optionally, application may modify other stream settings here
1406 * (such as jitter buffer parameters, codec ptime, etc.)
1407 */
Benny Prijono91e567e2007-12-28 08:51:58 +00001408 si->jb_init = pjsua_var.media_cfg.jb_init;
1409 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1410 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1411 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001412
Benny Prijono8147f402007-11-21 14:50:07 +00001413 /* Set SSRC */
Benny Prijono91e567e2007-12-28 08:51:58 +00001414 si->ssrc = call->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00001415
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00001416 /* Set RTP timestamp & sequence, normally these value are intialized
1417 * automatically when stream session created, but for some cases (e.g:
1418 * call reinvite, call update) timestamp and sequence need to be kept
1419 * contigue.
1420 */
1421 si->rtp_ts = call->rtp_tx_ts;
1422 si->rtp_seq = call->rtp_tx_seq;
1423 si->rtp_seq_ts_set = call->rtp_tx_seq_ts_set;
1424
Benny Prijonoc97608e2007-03-23 16:34:20 +00001425 /* Create session based on session info. */
1426 status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
1427 &call->med_tp,
1428 call, &call->session );
1429 if (status != PJ_SUCCESS) {
1430 return status;
1431 }
1432
1433 /* If DTMF callback is installed by application, install our
1434 * callback to the session.
1435 */
1436 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1437 pjmedia_session_set_dtmf_callback(call->session, 0,
1438 &dtmf_callback,
Benny Prijonod8179652008-01-23 20:39:07 +00001439 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00001440 }
1441
1442 /* Get the port interface of the first stream in the session.
1443 * We need the port interface to add to the conference bridge.
1444 */
1445 pjmedia_session_get_port(call->session, 0, &media_port);
1446
Benny Prijonofc13bf62008-02-20 08:56:15 +00001447 /* Notify application about stream creation.
1448 * Note: application may modify media_port to point to different
1449 * media port
1450 */
1451 if (pjsua_var.ua_cfg.cb.on_stream_created) {
1452 pjsua_var.ua_cfg.cb.on_stream_created(call_id, call->session,
1453 0, &media_port);
1454 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001455
1456 /*
1457 * Add the call to conference bridge.
1458 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001459 {
1460 char tmp[PJSIP_MAX_URL_SIZE];
1461 pj_str_t port_name;
1462
1463 port_name.ptr = tmp;
1464 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1465 call->inv->dlg->remote.info->uri,
1466 tmp, sizeof(tmp));
1467 if (port_name.slen < 1) {
1468 port_name = pj_str("call");
1469 }
1470 status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
1471 media_port,
1472 &port_name,
1473 (unsigned*)&call->conf_slot);
1474 if (status != PJ_SUCCESS) {
1475 return status;
1476 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001477 }
1478
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001479 /* Call media direction */
Benny Prijono91e567e2007-12-28 08:51:58 +00001480 call->media_dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001481
1482 /* Call media state */
1483 if (call->local_hold)
1484 call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
1485 else if (call->media_dir == PJMEDIA_DIR_DECODING)
1486 call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
1487 else
1488 call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001489 }
1490
1491 /* Print info. */
1492 {
1493 char info[80];
1494 int info_len = 0;
1495 unsigned i;
1496
1497 for (i=0; i<sess_info.stream_cnt; ++i) {
1498 int len;
1499 const char *dir;
1500 pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
1501
1502 switch (strm_info->dir) {
1503 case PJMEDIA_DIR_NONE:
1504 dir = "inactive";
1505 break;
1506 case PJMEDIA_DIR_ENCODING:
1507 dir = "sendonly";
1508 break;
1509 case PJMEDIA_DIR_DECODING:
1510 dir = "recvonly";
1511 break;
1512 case PJMEDIA_DIR_ENCODING_DECODING:
1513 dir = "sendrecv";
1514 break;
1515 default:
1516 dir = "unknown";
1517 break;
1518 }
1519 len = pj_ansi_sprintf( info+info_len,
1520 ", stream #%d: %.*s (%s)", i,
1521 (int)strm_info->fmt.encoding_name.slen,
1522 strm_info->fmt.encoding_name.ptr,
1523 dir);
1524 if (len > 0)
1525 info_len += len;
1526 }
1527 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
1528 }
1529
1530 return PJ_SUCCESS;
1531}
1532
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001533/*
1534 * Get maxinum number of conference ports.
1535 */
1536PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
1537{
1538 return pjsua_var.media_cfg.max_media_ports;
1539}
1540
1541
1542/*
1543 * Get current number of active ports in the bridge.
1544 */
1545PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
1546{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00001547 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001548 unsigned count = PJ_ARRAY_SIZE(ports);
1549 pj_status_t status;
1550
1551 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
1552 if (status != PJ_SUCCESS)
1553 count = 0;
1554
1555 return count;
1556}
1557
1558
1559/*
1560 * Enumerate all conference ports.
1561 */
1562PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
1563 unsigned *count)
1564{
1565 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
1566}
1567
1568
1569/*
1570 * Get information about the specified conference port
1571 */
1572PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
1573 pjsua_conf_port_info *info)
1574{
1575 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00001576 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001577 pj_status_t status;
1578
1579 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
1580 if (status != PJ_SUCCESS)
1581 return status;
1582
Benny Prijonoac623b32006-07-03 15:19:31 +00001583 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001584 info->slot_id = id;
1585 info->name = cinfo.name;
1586 info->clock_rate = cinfo.clock_rate;
1587 info->channel_count = cinfo.channel_count;
1588 info->samples_per_frame = cinfo.samples_per_frame;
1589 info->bits_per_sample = cinfo.bits_per_sample;
1590
1591 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00001592 info->listener_cnt = cinfo.listener_cnt;
1593 for (i=0; i<cinfo.listener_cnt; ++i) {
1594 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001595 }
1596
1597 return PJ_SUCCESS;
1598}
1599
1600
1601/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00001602 * Add arbitrary media port to PJSUA's conference bridge.
1603 */
1604PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
1605 pjmedia_port *port,
1606 pjsua_conf_port_id *p_id)
1607{
1608 pj_status_t status;
1609
1610 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1611 port, NULL, (unsigned*)p_id);
1612 if (status != PJ_SUCCESS) {
1613 if (p_id)
1614 *p_id = PJSUA_INVALID_ID;
1615 }
1616
1617 return status;
1618}
1619
1620
1621/*
1622 * Remove arbitrary slot from the conference bridge.
1623 */
1624PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
1625{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001626 pj_status_t status;
1627
1628 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
1629 check_snd_dev_idle();
1630
1631 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00001632}
1633
1634
1635/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001636 * Establish unidirectional media flow from souce to sink.
1637 */
1638PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
1639 pjsua_conf_port_id sink)
1640{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001641 /* If sound device idle timer is active, cancel it first. */
1642 if (pjsua_var.snd_idle_timer.id) {
1643 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
1644 pjsua_var.snd_idle_timer.id = PJ_FALSE;
1645 }
1646
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001647
Benny Prijonof798e502009-03-09 13:08:16 +00001648 /* For audio switchboard (i.e. APS-Direct):
1649 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001650 * (format, clock rate, channel count) must match to peer's.
1651 * Note that sound device can be reopened only if it doesn't have
1652 * any connection.
1653 */
Benny Prijonof798e502009-03-09 13:08:16 +00001654 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001655 pjmedia_conf_port_info port0_info;
1656 pjmedia_conf_port_info peer_info;
1657 unsigned peer_id;
1658 pj_bool_t need_reopen = PJ_FALSE;
1659 pj_status_t status;
1660
1661 peer_id = (source!=0)? source : sink;
1662 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
1663 &peer_info);
1664 pj_assert(status == PJ_SUCCESS);
1665
1666 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
1667 pj_assert(status == PJ_SUCCESS);
1668
1669 /* Check if sound device is instantiated. */
1670 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1671 !pjsua_var.no_snd);
1672
1673 /* Check if sound device need to reopen because it needs to modify
1674 * settings to match its peer. Sound device must be idle in this case
1675 * though.
1676 */
1677 if (!need_reopen &&
1678 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
1679 {
1680 need_reopen = (peer_info.format.id != port0_info.format.id ||
1681 peer_info.format.bitrate != port0_info.format.bitrate ||
1682 peer_info.clock_rate != port0_info.clock_rate ||
1683 peer_info.channel_count != port0_info.channel_count);
1684 }
1685
1686 if (need_reopen) {
Benny Prijono10454dc2009-02-21 14:21:59 +00001687 pjmedia_aud_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001688
Benny Prijonof798e502009-03-09 13:08:16 +00001689 /* Create parameter based on peer info */
1690 status = create_aud_param(&param, pjsua_var.cap_dev,
1691 pjsua_var.play_dev,
1692 peer_info.clock_rate,
1693 peer_info.channel_count,
1694 peer_info.samples_per_frame,
1695 peer_info.bits_per_sample);
1696
1697 /* And peer format */
1698 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
1699 param.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
1700 param.ext_fmt = peer_info.format;
Benny Prijono10454dc2009-02-21 14:21:59 +00001701 }
1702
Benny Prijonof798e502009-03-09 13:08:16 +00001703 status = open_snd_dev(&param);
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001704 if (status != PJ_SUCCESS) {
1705 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1706 return status;
1707 }
1708 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001709
Benny Prijonof798e502009-03-09 13:08:16 +00001710 } else {
1711 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001712
Benny Prijonof798e502009-03-09 13:08:16 +00001713 /* Create sound port if none is instantiated */
1714 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
1715 !pjsua_var.no_snd)
1716 {
1717 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001718
Benny Prijonof798e502009-03-09 13:08:16 +00001719 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
1720 if (status != PJ_SUCCESS) {
1721 pjsua_perror(THIS_FILE, "Error opening sound device", status);
1722 return status;
1723 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001724 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001725
Benny Prijonof798e502009-03-09 13:08:16 +00001726 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00001727
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001728 return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
1729}
1730
1731
1732/*
1733 * Disconnect media flow from the source to destination port.
1734 */
1735PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
1736 pjsua_conf_port_id sink)
1737{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001738 pj_status_t status;
1739
1740 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00001741 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00001742
1743 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001744}
1745
1746
Benny Prijono6dd967c2006-12-26 02:27:14 +00001747/*
1748 * Adjust the signal level to be transmitted from the bridge to the
1749 * specified port by making it louder or quieter.
1750 */
1751PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
1752 float level)
1753{
1754 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
1755 (int)((level-1) * 128));
1756}
1757
1758/*
1759 * Adjust the signal level to be received from the specified port (to
1760 * the bridge) by making it louder or quieter.
1761 */
1762PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
1763 float level)
1764{
1765 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
1766 (int)((level-1) * 128));
1767}
1768
1769
1770/*
1771 * Get last signal level transmitted to or received from the specified port.
1772 */
1773PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
1774 unsigned *tx_level,
1775 unsigned *rx_level)
1776{
1777 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
1778 tx_level, rx_level);
1779}
1780
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001781/*****************************************************************************
1782 * File player.
1783 */
1784
Benny Prijonod5696da2007-07-17 16:25:45 +00001785static char* get_basename(const char *path, unsigned len)
1786{
1787 char *p = ((char*)path) + len;
1788
1789 if (len==0)
1790 return p;
1791
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001792 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00001793
1794 return (p==path) ? p : p+1;
1795}
1796
1797
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001798/*
1799 * Create a file player, and automatically connect this player to
1800 * the conference bridge.
1801 */
1802PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1803 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001804 pjsua_player_id *p_id)
1805{
1806 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001807 char path[PJ_MAXPATH];
Benny Prijonod5696da2007-07-17 16:25:45 +00001808 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001809 pjmedia_port *port;
1810 pj_status_t status;
1811
1812 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1813 return PJ_ETOOMANY;
1814
1815 PJSUA_LOCK();
1816
1817 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1818 if (pjsua_var.player[file_id].port == NULL)
1819 break;
1820 }
1821
1822 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1823 /* This is unexpected */
1824 PJSUA_UNLOCK();
1825 pj_assert(0);
1826 return PJ_EBUG;
1827 }
1828
1829 pj_memcpy(path, filename->ptr, filename->slen);
1830 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00001831
1832 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
1833 if (!pool) {
1834 PJSUA_UNLOCK();
1835 return PJ_ENOMEM;
1836 }
1837
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00001838 status = pjmedia_wav_player_port_create(
1839 pool, path,
1840 pjsua_var.mconf_cfg.samples_per_frame *
1841 1000 / pjsua_var.media_cfg.channel_count /
1842 pjsua_var.media_cfg.clock_rate,
1843 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001844 if (status != PJ_SUCCESS) {
1845 PJSUA_UNLOCK();
1846 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001847 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001848 return status;
1849 }
1850
Benny Prijono5297af92008-03-18 13:40:40 +00001851 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001852 port, filename, &slot);
1853 if (status != PJ_SUCCESS) {
1854 pjmedia_port_destroy(port);
1855 PJSUA_UNLOCK();
Benny Prijono32e4f492007-01-24 00:44:26 +00001856 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1857 status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001858 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001859 return status;
1860 }
1861
Benny Prijonoa66c3312007-01-21 23:12:40 +00001862 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00001863 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001864 pjsua_var.player[file_id].port = port;
1865 pjsua_var.player[file_id].slot = slot;
1866
1867 if (p_id) *p_id = file_id;
1868
1869 ++pjsua_var.player_cnt;
1870
1871 PJSUA_UNLOCK();
1872 return PJ_SUCCESS;
1873}
1874
1875
1876/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00001877 * Create a file playlist media port, and automatically add the port
1878 * to the conference bridge.
1879 */
1880PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
1881 unsigned file_count,
1882 const pj_str_t *label,
1883 unsigned options,
1884 pjsua_player_id *p_id)
1885{
1886 unsigned slot, file_id, ptime;
Benny Prijonod5696da2007-07-17 16:25:45 +00001887 pj_pool_t *pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001888 pjmedia_port *port;
1889 pj_status_t status;
1890
1891 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1892 return PJ_ETOOMANY;
1893
1894 PJSUA_LOCK();
1895
1896 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1897 if (pjsua_var.player[file_id].port == NULL)
1898 break;
1899 }
1900
1901 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1902 /* This is unexpected */
1903 PJSUA_UNLOCK();
1904 pj_assert(0);
1905 return PJ_EBUG;
1906 }
1907
1908
1909 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
1910 pjsua_var.media_cfg.clock_rate;
1911
Benny Prijonod5696da2007-07-17 16:25:45 +00001912 pool = pjsua_pool_create("playlist", 1000, 1000);
1913 if (!pool) {
1914 PJSUA_UNLOCK();
1915 return PJ_ENOMEM;
1916 }
1917
1918 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001919 file_names, file_count,
1920 ptime, options, 0, &port);
1921 if (status != PJ_SUCCESS) {
1922 PJSUA_UNLOCK();
1923 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001924 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001925 return status;
1926 }
1927
Benny Prijonod5696da2007-07-17 16:25:45 +00001928 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00001929 port, &port->info.name, &slot);
1930 if (status != PJ_SUCCESS) {
1931 pjmedia_port_destroy(port);
1932 PJSUA_UNLOCK();
1933 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00001934 pj_pool_release(pool);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001935 return status;
1936 }
1937
1938 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00001939 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00001940 pjsua_var.player[file_id].port = port;
1941 pjsua_var.player[file_id].slot = slot;
1942
1943 if (p_id) *p_id = file_id;
1944
1945 ++pjsua_var.player_cnt;
1946
1947 PJSUA_UNLOCK();
1948 return PJ_SUCCESS;
1949
1950}
1951
1952
1953/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001954 * Get conference port ID associated with player.
1955 */
1956PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
1957{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001958 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001959 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1960
1961 return pjsua_var.player[id].slot;
1962}
1963
Benny Prijono469b1522006-12-26 03:05:17 +00001964/*
1965 * Get the media port for the player.
1966 */
Benny Prijonobe41d862008-01-18 13:24:28 +00001967PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00001968 pjmedia_port **p_port)
1969{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001970 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00001971 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1972 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1973
1974 *p_port = pjsua_var.player[id].port;
1975
1976 return PJ_SUCCESS;
1977}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001978
1979/*
1980 * Set playback position.
1981 */
1982PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
1983 pj_uint32_t samples)
1984{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001985 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001986 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00001987 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001988
1989 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
1990}
1991
1992
1993/*
1994 * Close the file, remove the player from the bridge, and free
1995 * resources associated with the file player.
1996 */
1997PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
1998{
Benny Prijonoa1e69682007-05-11 15:14:34 +00001999 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002000 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2001
2002 PJSUA_LOCK();
2003
2004 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002005 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002006 pjmedia_port_destroy(pjsua_var.player[id].port);
2007 pjsua_var.player[id].port = NULL;
2008 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002009 pj_pool_release(pjsua_var.player[id].pool);
2010 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002011 pjsua_var.player_cnt--;
2012 }
2013
2014 PJSUA_UNLOCK();
2015
2016 return PJ_SUCCESS;
2017}
2018
2019
2020/*****************************************************************************
2021 * File recorder.
2022 */
2023
2024/*
2025 * Create a file recorder, and automatically connect this recorder to
2026 * the conference bridge.
2027 */
2028PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00002029 unsigned enc_type,
2030 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002031 pj_ssize_t max_size,
2032 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002033 pjsua_recorder_id *p_id)
2034{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002035 enum Format
2036 {
2037 FMT_UNKNOWN,
2038 FMT_WAV,
2039 FMT_MP3,
2040 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002041 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002042 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002043 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00002044 int file_format;
Benny Prijonod5696da2007-07-17 16:25:45 +00002045 pj_pool_t *pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002046 pjmedia_port *port;
2047 pj_status_t status;
2048
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002049 /* Filename must present */
2050 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
2051
Benny Prijono00cae612006-07-31 15:19:36 +00002052 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002053 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002054
Benny Prijono8f310522006-10-20 11:08:49 +00002055 /* Don't support encoding type at present */
2056 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002057
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002058 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
2059 return PJ_ETOOMANY;
2060
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002061 /* Determine the file format */
2062 ext.ptr = filename->ptr + filename->slen - 4;
2063 ext.slen = 4;
2064
2065 if (pj_stricmp2(&ext, ".wav") == 0)
2066 file_format = FMT_WAV;
2067 else if (pj_stricmp2(&ext, ".mp3") == 0)
2068 file_format = FMT_MP3;
2069 else {
2070 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
2071 "determine file format for %.*s",
2072 (int)filename->slen, filename->ptr));
2073 return PJ_ENOTSUP;
2074 }
2075
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002076 PJSUA_LOCK();
2077
2078 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
2079 if (pjsua_var.recorder[file_id].port == NULL)
2080 break;
2081 }
2082
2083 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
2084 /* This is unexpected */
2085 PJSUA_UNLOCK();
2086 pj_assert(0);
2087 return PJ_EBUG;
2088 }
2089
2090 pj_memcpy(path, filename->ptr, filename->slen);
2091 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002092
Benny Prijonod5696da2007-07-17 16:25:45 +00002093 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2094 if (!pool) {
2095 PJSUA_UNLOCK();
2096 return PJ_ENOMEM;
2097 }
2098
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002099 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00002100 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002101 pjsua_var.media_cfg.clock_rate,
2102 pjsua_var.mconf_cfg.channel_count,
2103 pjsua_var.mconf_cfg.samples_per_frame,
2104 pjsua_var.mconf_cfg.bits_per_sample,
2105 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002106 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00002107 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002108 port = NULL;
2109 status = PJ_ENOTSUP;
2110 }
2111
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002112 if (status != PJ_SUCCESS) {
2113 PJSUA_UNLOCK();
2114 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonod5696da2007-07-17 16:25:45 +00002115 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002116 return status;
2117 }
2118
Benny Prijonod5696da2007-07-17 16:25:45 +00002119 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002120 port, filename, &slot);
2121 if (status != PJ_SUCCESS) {
2122 pjmedia_port_destroy(port);
2123 PJSUA_UNLOCK();
Benny Prijonod5696da2007-07-17 16:25:45 +00002124 pj_pool_release(pool);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002125 return status;
2126 }
2127
2128 pjsua_var.recorder[file_id].port = port;
2129 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00002130 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002131
2132 if (p_id) *p_id = file_id;
2133
2134 ++pjsua_var.rec_cnt;
2135
2136 PJSUA_UNLOCK();
2137 return PJ_SUCCESS;
2138}
2139
2140
2141/*
2142 * Get conference port associated with recorder.
2143 */
2144PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
2145{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002146 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2147 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002148 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2149
2150 return pjsua_var.recorder[id].slot;
2151}
2152
Benny Prijono469b1522006-12-26 03:05:17 +00002153/*
2154 * Get the media port for the recorder.
2155 */
2156PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
2157 pjmedia_port **p_port)
2158{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002159 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2160 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002161 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2162 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2163
2164 *p_port = pjsua_var.recorder[id].port;
2165 return PJ_SUCCESS;
2166}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002167
2168/*
2169 * Destroy recorder (this will complete recording).
2170 */
2171PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
2172{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002173 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
2174 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002175 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
2176
2177 PJSUA_LOCK();
2178
2179 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002180 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002181 pjmedia_port_destroy(pjsua_var.recorder[id].port);
2182 pjsua_var.recorder[id].port = NULL;
2183 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002184 pj_pool_release(pjsua_var.recorder[id].pool);
2185 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002186 pjsua_var.rec_cnt--;
2187 }
2188
2189 PJSUA_UNLOCK();
2190
2191 return PJ_SUCCESS;
2192}
2193
2194
2195/*****************************************************************************
2196 * Sound devices.
2197 */
2198
2199/*
2200 * Enum sound devices.
2201 */
Benny Prijonof798e502009-03-09 13:08:16 +00002202
2203PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002204 unsigned *count)
2205{
2206 unsigned i, dev_count;
2207
Benny Prijono10454dc2009-02-21 14:21:59 +00002208 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002209
2210 if (dev_count > *count) dev_count = *count;
2211
2212 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00002213 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002214
Benny Prijono10454dc2009-02-21 14:21:59 +00002215 status = pjmedia_aud_dev_get_info(i, &info[i]);
2216 if (status != PJ_SUCCESS)
2217 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002218 }
2219
2220 *count = dev_count;
2221
2222 return PJ_SUCCESS;
2223}
Benny Prijonof798e502009-03-09 13:08:16 +00002224
2225
Benny Prijono10454dc2009-02-21 14:21:59 +00002226PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
2227 unsigned *count)
2228{
2229 unsigned i, dev_count;
2230
2231 dev_count = pjmedia_aud_dev_count();
2232
2233 if (dev_count > *count) dev_count = *count;
2234 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
2235
2236 for (i=0; i<dev_count; ++i) {
2237 pjmedia_aud_dev_info ai;
2238 pj_status_t status;
2239
2240 status = pjmedia_aud_dev_get_info(i, &ai);
2241 if (status != PJ_SUCCESS)
2242 return status;
2243
2244 strncpy(info[i].name, ai.name, sizeof(info[i].name));
2245 info[i].name[sizeof(info[i].name)-1] = '\0';
2246 info[i].input_count = ai.input_count;
2247 info[i].output_count = ai.output_count;
2248 info[i].default_samples_per_sec = ai.default_samples_per_sec;
2249 }
2250
2251 *count = dev_count;
2252
2253 return PJ_SUCCESS;
2254}
Benny Prijono10454dc2009-02-21 14:21:59 +00002255
Benny Prijonof798e502009-03-09 13:08:16 +00002256/* Create audio device parameter to open the device */
2257static pj_status_t create_aud_param(pjmedia_aud_param *param,
2258 pjmedia_aud_dev_index capture_dev,
2259 pjmedia_aud_dev_index playback_dev,
2260 unsigned clock_rate,
2261 unsigned channel_count,
2262 unsigned samples_per_frame,
2263 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002264{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002265 pj_status_t status;
2266
Benny Prijono96e74f32009-02-22 12:00:12 +00002267 /* Normalize device ID with new convention about default device ID */
2268 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
2269 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
2270
Benny Prijono10454dc2009-02-21 14:21:59 +00002271 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00002272 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00002273 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00002274 pjsua_perror(THIS_FILE, "Error retrieving default audio "
2275 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00002276 return status;
2277 }
Benny Prijonof798e502009-03-09 13:08:16 +00002278 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
2279 param->rec_id = capture_dev;
2280 param->play_id = playback_dev;
2281 param->clock_rate = clock_rate;
2282 param->channel_count = channel_count;
2283 param->samples_per_frame = samples_per_frame;
2284 param->bits_per_sample = bits_per_sample;
2285
2286 /* Update the setting with user preference */
2287#define update_param(cap, field) \
2288 if (pjsua_var.aud_param.flags & cap) { \
2289 param->flags |= cap; \
2290 param->field = pjsua_var.aud_param.field; \
2291 }
2292 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
2293 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
2294 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
2295 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
2296#undef update_param
2297
Benny Prijono10454dc2009-02-21 14:21:59 +00002298 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00002299 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
2300 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
2301 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
2302 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
2303
Benny Prijono10454dc2009-02-21 14:21:59 +00002304 /* EC settings */
2305 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00002306 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
2307 param->ec_enabled = PJ_TRUE;
2308 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00002309 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00002310 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00002311 }
2312
Benny Prijonof798e502009-03-09 13:08:16 +00002313 return PJ_SUCCESS;
2314}
Benny Prijono26056d82006-10-11 16:03:41 +00002315
Benny Prijonof798e502009-03-09 13:08:16 +00002316/* Internal: the first time the audio device is opened (during app
2317 * startup), retrieve the audio settings such as volume level
2318 * so that aud_get_settings() will work.
2319 */
2320static pj_status_t update_initial_aud_param()
2321{
2322 pjmedia_aud_stream *strm;
2323 pjmedia_aud_param param;
2324 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00002325
Benny Prijonof798e502009-03-09 13:08:16 +00002326 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00002327
Benny Prijonof798e502009-03-09 13:08:16 +00002328 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00002329
Benny Prijonof798e502009-03-09 13:08:16 +00002330 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002331 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00002332 pjsua_perror(THIS_FILE, "Error audio stream "
2333 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002334 return status;
2335 }
2336
Benny Prijonof798e502009-03-09 13:08:16 +00002337#define update_saved_param(cap, field) \
2338 if (param.flags & cap) { \
2339 pjsua_var.aud_param.flags |= cap; \
2340 pjsua_var.aud_param.field = param.field; \
2341 }
2342
2343 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
2344 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
2345 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
2346 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
2347#undef update_saved_param
2348
2349 return PJ_SUCCESS;
2350}
2351
2352/* Get format name */
2353static const char *get_fmt_name(pj_uint32_t id)
2354{
2355 static char name[8];
2356
2357 if (id == PJMEDIA_FORMAT_L16)
2358 return "PCM";
2359 pj_memcpy(name, &id, 4);
2360 name[4] = '\0';
2361 return name;
2362}
2363
2364/* Open sound device with the setting. */
2365static pj_status_t open_snd_dev(pjmedia_aud_param *param)
2366{
2367 pjmedia_port *conf_port;
2368 pj_status_t status;
2369
2370 PJ_ASSERT_RETURN(param, PJ_EINVAL);
2371
2372 /* Check if NULL sound device is used */
2373 if (NULL_SND_DEV_ID==param->rec_id || NULL_SND_DEV_ID==param->play_id) {
2374 return pjsua_set_null_snd_dev();
2375 }
2376
2377 /* Close existing sound port */
2378 close_snd_dev();
2379
2380 /* Create memory pool for sound device. */
2381 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2382 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2383
2384
2385 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
2386 get_fmt_name(param->ext_fmt.id),
2387 param->clock_rate, param->channel_count,
2388 param->samples_per_frame / param->channel_count * 1000 /
2389 param->clock_rate));
2390
2391 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
2392 param, &pjsua_var.snd_port);
2393 if (status != PJ_SUCCESS)
2394 return status;
2395
2396 /* Get the port0 of the conference bridge. */
2397 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2398 pj_assert(conf_port != NULL);
2399
2400 /* For conference bridge, resample if necessary if the bridge's
2401 * clock rate is different than the sound device's clock rate.
2402 */
2403 if (!pjsua_var.is_mswitch &&
2404 param->ext_fmt.id == PJMEDIA_FORMAT_PCM &&
2405 conf_port->info.clock_rate != param->clock_rate)
2406 {
2407 pjmedia_port *resample_port;
2408 unsigned resample_opt = 0;
2409
2410 if (pjsua_var.media_cfg.quality >= 3 &&
2411 pjsua_var.media_cfg.quality <= 4)
2412 {
2413 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
2414 }
2415 else if (pjsua_var.media_cfg.quality < 3) {
2416 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
2417 }
2418
2419 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
2420 conf_port,
2421 param->clock_rate,
2422 resample_opt,
2423 &resample_port);
2424 if (status != PJ_SUCCESS) {
2425 char errmsg[PJ_ERR_MSG_SIZE];
2426 pj_strerror(status, errmsg, sizeof(errmsg));
2427 PJ_LOG(4, (THIS_FILE,
2428 "Error creating resample port: %s",
2429 errmsg));
2430 close_snd_dev();
2431 return status;
2432 }
2433
2434 conf_port = resample_port;
2435 }
2436
2437 /* Otherwise for audio switchboard, the switch's port0 setting is
2438 * derived from the sound device setting, so update the setting.
2439 */
2440 if (pjsua_var.is_mswitch) {
2441 pj_memcpy(&conf_port->info.format, &param->ext_fmt,
2442 sizeof(conf_port->info.format));
2443 conf_port->info.clock_rate = param->clock_rate;
2444 conf_port->info.samples_per_frame = param->samples_per_frame;
2445 conf_port->info.channel_count = param->channel_count;
2446 conf_port->info.bits_per_sample = 16;
2447 }
2448
2449 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00002450 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
2451 conf_port );
2452 if (status != PJ_SUCCESS) {
2453 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
2454 "sound device", status);
2455 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2456 pjsua_var.snd_port = NULL;
2457 return status;
2458 }
2459
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002460 /* Save the device IDs */
Benny Prijonof798e502009-03-09 13:08:16 +00002461 pjsua_var.cap_dev = param->rec_id;
2462 pjsua_var.play_dev = param->play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002463
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002464 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00002465 {
2466 pjmedia_aud_dev_info rec_info;
2467 pjmedia_aud_stream *strm;
2468 pjmedia_aud_param si;
2469 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002470
Benny Prijonof798e502009-03-09 13:08:16 +00002471 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2472 status = pjmedia_aud_stream_get_param(strm, &si);
2473 if (status == PJ_SUCCESS)
2474 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00002475
Benny Prijonof798e502009-03-09 13:08:16 +00002476 if (status==PJ_SUCCESS) {
2477 if (param->clock_rate != pjsua_var.media_cfg.clock_rate) {
2478 char tmp_buf[128];
2479 int tmp_buf_len = sizeof(tmp_buf);
2480
2481 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
2482 "%s (%dKHz)",
2483 rec_info.name,
2484 param->clock_rate/1000);
2485 pj_strset(&tmp, tmp_buf, tmp_buf_len);
2486 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
2487 } else {
2488 pjmedia_conf_set_port0_name(pjsua_var.mconf,
2489 pj_cstr(&tmp, rec_info.name));
2490 }
2491 }
2492
2493 /* Any error is not major, let it through */
2494 status = PJ_SUCCESS;
2495 };
2496
2497 /* If this is the first time the audio device is open, retrieve some
2498 * settings from the device (such as volume settings) so that the
2499 * pjsua_snd_get_setting() work.
2500 */
2501 if (pjsua_var.aud_open_cnt == 0) {
2502 update_initial_aud_param();
2503 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00002504 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00002505
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002506 return PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00002507}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002508
Nanang Izzuddin8465c682009-03-04 17:23:25 +00002509
Benny Prijonof798e502009-03-09 13:08:16 +00002510/* Close existing sound device */
2511static void close_snd_dev(void)
2512{
2513 /* Close sound device */
2514 if (pjsua_var.snd_port) {
2515 pjmedia_aud_dev_info cap_info, play_info;
2516 pjmedia_aud_stream *strm;
2517 pjmedia_aud_param param;
2518
2519 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2520 pjmedia_aud_stream_get_param(strm, &param);
2521
2522 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
2523 cap_info.name[0] = '\0';
2524 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
2525 play_info.name[0] = '\0';
2526
2527 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
2528 "%s sound capture device",
2529 play_info.name, cap_info.name));
2530
2531 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
2532 pjmedia_snd_port_destroy(pjsua_var.snd_port);
2533 pjsua_var.snd_port = NULL;
2534 }
2535
2536 /* Close null sound device */
2537 if (pjsua_var.null_snd) {
2538 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
2539 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
2540 pjsua_var.null_snd = NULL;
2541 }
2542
2543 if (pjsua_var.snd_pool)
2544 pj_pool_release(pjsua_var.snd_pool);
2545 pjsua_var.snd_pool = NULL;
2546}
2547
2548
2549/*
2550 * Select or change sound device. Application may call this function at
2551 * any time to replace current sound device.
2552 */
2553PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
2554 int playback_dev)
2555{
2556 unsigned alt_cr_cnt = 1;
2557 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
2558 unsigned i;
2559 pj_status_t status = -1;
2560
2561 /* Set default clock rate */
2562 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
2563 if (alt_cr[0] == 0)
2564 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
2565
2566 /* Allow retrying of different clock rate if we're using conference
2567 * bridge (meaning audio format is always PCM), otherwise lock on
2568 * to one clock rate.
2569 */
2570 if (pjsua_var.is_mswitch) {
2571 alt_cr_cnt = 1;
2572 } else {
2573 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
2574 }
2575
2576 /* Attempts to open the sound device with different clock rates */
2577 for (i=0; i<alt_cr_cnt; ++i) {
2578 pjmedia_aud_param param;
2579 unsigned samples_per_frame;
2580
2581 /* Create the default audio param */
2582 samples_per_frame = alt_cr[i] *
2583 pjsua_var.media_cfg.audio_frame_ptime *
2584 pjsua_var.media_cfg.channel_count / 1000;
2585 status = create_aud_param(&param, capture_dev, playback_dev,
2586 alt_cr[i], pjsua_var.media_cfg.channel_count,
2587 samples_per_frame, 16);
2588 if (status != PJ_SUCCESS)
2589 return status;
2590
2591 /* Open! */
2592 status = open_snd_dev(&param);
2593 if (status == PJ_SUCCESS)
2594 break;
2595 }
2596
2597 if (status != PJ_SUCCESS) {
2598 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
2599 return status;
2600 }
2601
2602 return PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002603}
2604
2605
2606/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00002607 * Get currently active sound devices. If sound devices has not been created
2608 * (for example when pjsua_start() is not called), it is possible that
2609 * the function returns PJ_SUCCESS with -1 as device IDs.
2610 */
2611PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
2612 int *playback_dev)
2613{
2614 if (capture_dev) {
2615 *capture_dev = pjsua_var.cap_dev;
2616 }
2617 if (playback_dev) {
2618 *playback_dev = pjsua_var.play_dev;
2619 }
2620
2621 return PJ_SUCCESS;
2622}
2623
2624
2625/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002626 * Use null sound device.
2627 */
2628PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
2629{
2630 pjmedia_port *conf_port;
2631 pj_status_t status;
2632
2633 /* Close existing sound device */
2634 close_snd_dev();
2635
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002636 /* Create memory pool for sound device. */
2637 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
2638 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
2639
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002640 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
2641
2642 /* Get the port0 of the conference bridge. */
2643 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
2644 pj_assert(conf_port != NULL);
2645
2646 /* Create master port, connecting port0 of the conference bridge to
2647 * a null port.
2648 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002649 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002650 conf_port, 0, &pjsua_var.null_snd);
2651 if (status != PJ_SUCCESS) {
2652 pjsua_perror(THIS_FILE, "Unable to create null sound device",
2653 status);
2654 return status;
2655 }
2656
2657 /* Start the master port */
2658 status = pjmedia_master_port_start(pjsua_var.null_snd);
2659 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2660
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002661 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2662 pjsua_var.play_dev = NULL_SND_DEV_ID;
2663
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002664 return PJ_SUCCESS;
2665}
2666
2667
Benny Prijonoe909eac2006-07-27 22:04:56 +00002668
2669/*
2670 * Use no device!
2671 */
2672PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2673{
2674 /* Close existing sound device */
2675 close_snd_dev();
2676
2677 pjsua_var.no_snd = PJ_TRUE;
2678 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2679}
2680
2681
Benny Prijonof20687a2006-08-04 18:27:19 +00002682/*
2683 * Configure the AEC settings of the sound port.
2684 */
Benny Prijono5da50432006-08-07 10:24:52 +00002685PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00002686{
2687 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2688
2689 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00002690 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
2691 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00002692
2693 return PJ_SUCCESS;
2694}
2695
2696
2697/*
2698 * Get current AEC tail length.
2699 */
Benny Prijono22dfe592006-08-06 12:07:13 +00002700PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00002701{
2702 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2703 return PJ_SUCCESS;
2704}
2705
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002706
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002707/*
Benny Prijonof798e502009-03-09 13:08:16 +00002708 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002709 */
Benny Prijonof798e502009-03-09 13:08:16 +00002710PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002711{
Benny Prijonof798e502009-03-09 13:08:16 +00002712 return pjsua_var.snd_port != NULL;
2713}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002714
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002715
Benny Prijonof798e502009-03-09 13:08:16 +00002716/*
2717 * Configure sound device setting to the sound device being used.
2718 */
2719PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
2720 const void *pval,
2721 pj_bool_t keep)
2722{
Benny Prijono09b0ff62009-03-10 12:07:51 +00002723 pj_status_t status;
2724
Benny Prijonof798e502009-03-09 13:08:16 +00002725 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00002726 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00002727 return PJMEDIA_EAUD_INVCAP;
2728 }
2729
Benny Prijono09b0ff62009-03-10 12:07:51 +00002730 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00002731 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00002732 pjmedia_aud_stream *strm;
2733
2734 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00002735 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00002736 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00002737 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00002738 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00002739
2740 if (status != PJ_SUCCESS)
2741 return status;
2742
2743 /* Save in internal param for later device open */
2744 if (keep) {
2745 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
2746 cap, pval);
2747 }
2748
2749 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00002750}
2751
2752/*
2753 * Retrieve a sound device setting.
2754 */
2755PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
2756 void *pval)
2757{
2758 /* If sound device has never been opened before, open it to
2759 * retrieve the initial setting from the device (e.g. audio
2760 * volume)
2761 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00002762 if (pjsua_var.aud_open_cnt==0) {
2763 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00002764 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00002765 close_snd_dev();
2766 }
Benny Prijonof798e502009-03-09 13:08:16 +00002767
2768 if (pjsua_snd_is_active()) {
2769 /* Sound is active, retrieve from device directly */
2770 pjmedia_aud_stream *strm;
2771
2772 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2773 return pjmedia_aud_stream_get_cap(strm, cap, pval);
2774 } else {
2775 /* Otherwise retrieve from internal param */
2776 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
2777 cap, pval);
2778 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00002779}
2780
2781
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002782/*****************************************************************************
2783 * Codecs.
2784 */
2785
2786/*
2787 * Enum all supported codecs in the system.
2788 */
2789PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
2790 unsigned *p_count )
2791{
2792 pjmedia_codec_mgr *codec_mgr;
2793 pjmedia_codec_info info[32];
2794 unsigned i, count, prio[32];
2795 pj_status_t status;
2796
2797 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2798 count = PJ_ARRAY_SIZE(info);
2799 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
2800 if (status != PJ_SUCCESS) {
2801 *p_count = 0;
2802 return status;
2803 }
2804
2805 if (count > *p_count) count = *p_count;
2806
2807 for (i=0; i<count; ++i) {
2808 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
2809 id[i].codec_id = pj_str(id[i].buf_);
2810 id[i].priority = (pj_uint8_t) prio[i];
2811 }
2812
2813 *p_count = count;
2814
2815 return PJ_SUCCESS;
2816}
2817
2818
2819/*
2820 * Change codec priority.
2821 */
2822PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
2823 pj_uint8_t priority )
2824{
Benny Prijono88accae2008-06-26 15:48:14 +00002825 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002826 pjmedia_codec_mgr *codec_mgr;
2827
2828 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2829
Benny Prijono88accae2008-06-26 15:48:14 +00002830 if (codec_id->slen==1 && *codec_id->ptr=='*')
2831 codec_id = &all;
2832
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002833 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
2834 priority);
2835}
2836
2837
2838/*
2839 * Get codec parameters.
2840 */
2841PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
2842 pjmedia_codec_param *param )
2843{
Benny Prijono88accae2008-06-26 15:48:14 +00002844 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002845 const pjmedia_codec_info *info;
2846 pjmedia_codec_mgr *codec_mgr;
2847 unsigned count = 1;
2848 pj_status_t status;
2849
2850 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
2851
Benny Prijono88accae2008-06-26 15:48:14 +00002852 if (codec_id->slen==1 && *codec_id->ptr=='*')
2853 codec_id = &all;
2854
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002855 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
2856 &count, &info, NULL);
2857 if (status != PJ_SUCCESS)
2858 return status;
2859
2860 if (count != 1)
2861 return PJ_ENOTFOUND;
2862
2863 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
2864 return status;
2865}
2866
2867
2868/*
2869 * Set codec parameters.
2870 */
2871PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
2872 const pjmedia_codec_param *param)
2873{
Benny Prijono00cae612006-07-31 15:19:36 +00002874 PJ_UNUSED_ARG(id);
2875 PJ_UNUSED_ARG(param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002876 PJ_TODO(set_codec_param);
2877 return PJ_SUCCESS;
2878}