blob: 6f9ae1731fb4ef330f34e53e9d3f4a892c32f738 [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
Nanang Izzuddina62ffc92011-05-05 06:14:19 +00003 * Copyright (C) 2008-2011 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 */
Sauw Ming98766c72011-03-11 06:57:24 +000039static pj_status_t open_snd_dev(pjmedia_snd_port_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
Nanang Izzuddin50fae732011-03-22 09:49:23 +000061
Benny Prijonoeebe9af2006-06-13 22:57:13 +000062/**
63 * Init media subsystems.
64 */
65pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
66{
Benny Prijonoba5926a2007-05-02 11:29:37 +000067 pj_str_t codec_id = {NULL, 0};
Benny Prijono0498d902006-06-19 14:49:14 +000068 unsigned opt;
Benny Prijono35fc1eb2011-07-15 09:51:46 +000069 pjmedia_audio_codec_config codec_cfg;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000070 pj_status_t status;
71
Benny Prijonofc24e692007-01-27 18:31:51 +000072 /* To suppress warning about unused var when all codecs are disabled */
73 PJ_UNUSED_ARG(codec_id);
74
Benny Prijonob90fd382011-09-18 14:59:56 +000075 pj_log_push_indent();
76
Benny Prijonof798e502009-03-09 13:08:16 +000077 /* Specify which audio device settings are save-able */
78 pjsua_var.aud_svmask = 0xFFFFFFFF;
79 /* These are not-settable */
80 pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
81 PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER |
82 PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER);
Benny Prijonoe506c8c2009-03-10 13:28:43 +000083 /* EC settings use different API */
84 pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EC |
85 PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijonof798e502009-03-09 13:08:16 +000086
Benny Prijonoeebe9af2006-06-13 22:57:13 +000087 /* Copy configuration */
Benny Prijonof76e1392008-06-06 14:51:48 +000088 pjsua_media_config_dup(pjsua_var.pool, &pjsua_var.media_cfg, cfg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000089
90 /* Normalize configuration */
Benny Prijono50f19b32008-03-11 13:15:43 +000091 if (pjsua_var.media_cfg.snd_clock_rate == 0) {
92 pjsua_var.media_cfg.snd_clock_rate = pjsua_var.media_cfg.clock_rate;
93 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +000094
95 if (pjsua_var.media_cfg.has_ioqueue &&
96 pjsua_var.media_cfg.thread_cnt == 0)
97 {
98 pjsua_var.media_cfg.thread_cnt = 1;
99 }
100
101 if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
102 pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
103 }
104
105 /* Create media endpoint. */
106 status = pjmedia_endpt_create(&pjsua_var.cp.factory,
107 pjsua_var.media_cfg.has_ioqueue? NULL :
108 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
109 pjsua_var.media_cfg.thread_cnt,
110 &pjsua_var.med_endpt);
111 if (status != PJ_SUCCESS) {
112 pjsua_perror(THIS_FILE,
113 "Media stack initialization has returned error",
114 status);
Benny Prijonob90fd382011-09-18 14:59:56 +0000115 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000116 }
117
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000118 /*
119 * Register all codecs
120 */
121 pjmedia_audio_codec_config_default(&codec_cfg);
122 codec_cfg.speex.quality = pjsua_var.media_cfg.quality;
123 codec_cfg.speex.complexity = -1;
124 codec_cfg.ilbc.mode = pjsua_var.media_cfg.ilbc_mode;
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000125
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000126#if PJMEDIA_HAS_PASSTHROUGH_CODECS
127 /* Register passthrough codecs */
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000128 {
129 unsigned aud_idx;
130 unsigned ext_fmt_cnt = 0;
131 pjmedia_format ext_fmts[32];
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000132
133 /* List extended formats supported by audio devices */
134 for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) {
135 pjmedia_aud_dev_info aud_info;
136 unsigned i;
137
138 status = pjmedia_aud_dev_get_info(aud_idx, &aud_info);
139 if (status != PJ_SUCCESS) {
140 pjsua_perror(THIS_FILE, "Error querying audio device info",
141 status);
Benny Prijonob90fd382011-09-18 14:59:56 +0000142 goto on_error;
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000143 }
144
145 /* Collect extended formats supported by this audio device */
146 for (i = 0; i < aud_info.ext_fmt_cnt; ++i) {
147 unsigned j;
148 pj_bool_t is_listed = PJ_FALSE;
149
150 /* See if this extended format is already in the list */
151 for (j = 0; j < ext_fmt_cnt && !is_listed; ++j) {
152 if (ext_fmts[j].id == aud_info.ext_fmt[i].id &&
153 ext_fmts[j].bitrate == aud_info.ext_fmt[i].bitrate)
154 {
155 is_listed = PJ_TRUE;
156 }
157 }
158
159 /* Put this format into the list, if it is not in the list */
160 if (!is_listed)
161 ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i];
162
163 pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts));
164 }
165 }
166
167 /* Init the passthrough codec with supported formats only */
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000168 codec_cfg.passthrough.setting.fmt_cnt = ext_fmt_cnt;
169 codec_cfg.passthrough.setting.fmts = ext_fmts;
170 codec_cfg.passthrough.setting.ilbc_mode = cfg->ilbc_mode;
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000171 }
172#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
173
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000174 /* Register all codecs */
175 status = pjmedia_codec_register_audio_codecs(pjsua_var.med_endpt,
176 &codec_cfg);
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000177 if (status != PJ_SUCCESS) {
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000178 PJ_PERROR(1,(THIS_FILE, status, "Error registering codecs"));
Benny Prijonob90fd382011-09-18 14:59:56 +0000179 goto on_error;
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000180 }
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000181
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000182 /* Set speex/16000 to higher priority*/
183 codec_id = pj_str("speex/16000");
184 pjmedia_codec_mgr_set_codec_priority(
185 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
186 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
187
188 /* Set speex/8000 to next higher priority*/
189 codec_id = pj_str("speex/8000");
190 pjmedia_codec_mgr_set_codec_priority(
191 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
192 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000193
194 /* Disable ALL L16 codecs */
195 codec_id = pj_str("L16");
196 pjmedia_codec_mgr_set_codec_priority(
197 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
198 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
199
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000200
201 /* Save additional conference bridge parameters for future
202 * reference.
203 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000204 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000205 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000206 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
207 pjsua_var.mconf_cfg.channel_count *
208 pjsua_var.media_cfg.audio_frame_ptime /
209 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000210
Benny Prijono0498d902006-06-19 14:49:14 +0000211 /* Init options for conference bridge. */
212 opt = PJMEDIA_CONF_NO_DEVICE;
213 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000214 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000215 {
216 opt |= PJMEDIA_CONF_SMALL_FILTER;
217 }
218 else if (pjsua_var.media_cfg.quality < 3) {
219 opt |= PJMEDIA_CONF_USE_LINEAR;
220 }
221
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000222 /* Init conference bridge. */
223 status = pjmedia_conf_create(pjsua_var.pool,
224 pjsua_var.media_cfg.max_media_ports,
225 pjsua_var.media_cfg.clock_rate,
226 pjsua_var.mconf_cfg.channel_count,
227 pjsua_var.mconf_cfg.samples_per_frame,
228 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000229 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000230 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000231 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000232 status);
Benny Prijonob90fd382011-09-18 14:59:56 +0000233 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000234 }
235
Benny Prijonof798e502009-03-09 13:08:16 +0000236 /* Are we using the audio switchboard (a.k.a APS-Direct)? */
237 pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
238 ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
239
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000240 /* Create null port just in case user wants to use null sound. */
241 status = pjmedia_null_port_create(pjsua_var.pool,
242 pjsua_var.media_cfg.clock_rate,
243 pjsua_var.mconf_cfg.channel_count,
244 pjsua_var.mconf_cfg.samples_per_frame,
245 pjsua_var.mconf_cfg.bits_per_sample,
246 &pjsua_var.null_port);
247 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
248
Nanang Izzuddin69b69ae2009-04-14 15:18:30 +0000249#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
250 /* Initialize SRTP library. */
251 status = pjmedia_srtp_init_lib();
252 if (status != PJ_SUCCESS) {
253 pjsua_perror(THIS_FILE, "Error initializing SRTP library",
254 status);
Benny Prijonob90fd382011-09-18 14:59:56 +0000255 goto on_error;
Nanang Izzuddin69b69ae2009-04-14 15:18:30 +0000256 }
257#endif
258
Benny Prijono9f468d12011-07-07 07:46:33 +0000259 /* Video */
260#if PJMEDIA_HAS_VIDEO
261 status = pjsua_vid_subsys_init();
262 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +0000263 goto on_error;
Benny Prijono9f468d12011-07-07 07:46:33 +0000264#endif
265
Benny Prijonob90fd382011-09-18 14:59:56 +0000266 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000267 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +0000268
269on_error:
270 pj_log_pop_indent();
271 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000272}
273
274
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000275/* Check if sound device is idle. */
276static void check_snd_dev_idle()
277{
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000278 unsigned call_cnt;
279
280 /* Get the call count, we shouldn't close the sound device when there is
281 * any calls active.
282 */
283 call_cnt = pjsua_call_get_count();
284
285 /* When this function is called from pjsua_media_channel_deinit() upon
286 * disconnecting call, actually the call count hasn't been updated/
287 * decreased. So we put additional check here, if there is only one
288 * call and it's in DISCONNECTED state, there is actually no active
289 * call.
290 */
291 if (call_cnt == 1) {
292 pjsua_call_id call_id;
293 pj_status_t status;
294
295 status = pjsua_enum_calls(&call_id, &call_cnt);
296 if (status == PJ_SUCCESS && call_cnt > 0 &&
297 !pjsua_call_is_active(call_id))
298 {
299 call_cnt = 0;
300 }
301 }
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000302
303 /* Activate sound device auto-close timer if sound device is idle.
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000304 * It is idle when there is no port connection in the bridge and
305 * there is no active call.
Benny Prijono2d647722011-07-13 03:05:22 +0000306 *
307 * Note: this block is now valid if no snd dev is used because of #1299
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000308 */
Benny Prijono2d647722011-07-13 03:05:22 +0000309 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL ||
310 pjsua_var.no_snd) &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000311 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
312 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000313 call_cnt == 0 &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000314 pjsua_var.media_cfg.snd_auto_close_time >= 0)
315 {
316 pj_time_val delay;
317
318 delay.msec = 0;
319 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
320
321 pjsua_var.snd_idle_timer.id = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000322 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000323 &delay);
324 }
325}
326
327
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000328/* Timer callback to close sound device */
329static void close_snd_timer_cb( pj_timer_heap_t *th,
330 pj_timer_entry *entry)
331{
332 PJ_UNUSED_ARG(th);
333
Benny Prijono0f711b42009-05-06 19:08:43 +0000334 PJSUA_LOCK();
335 if (entry->id) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000336 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
Benny Prijono0f711b42009-05-06 19:08:43 +0000337 pjsua_var.media_cfg.snd_auto_close_time));
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000338
Benny Prijono0f711b42009-05-06 19:08:43 +0000339 entry->id = PJ_FALSE;
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000340
Benny Prijono0f711b42009-05-06 19:08:43 +0000341 close_snd_dev();
342 }
343 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000344}
345
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000346
347/*
348 * Start pjsua media subsystem.
349 */
350pj_status_t pjsua_media_subsys_start(void)
351{
352 pj_status_t status;
353
Benny Prijonob90fd382011-09-18 14:59:56 +0000354 pj_log_push_indent();
355
Benny Prijono0bc99a92011-03-17 04:34:43 +0000356#if DISABLED_FOR_TICKET_1185
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000357 /* Create media for calls, if none is specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000358 if (pjsua_var.calls[0].media[0].tp == NULL) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000359 pjsua_transport_config transport_cfg;
360
361 /* Create default transport config */
362 pjsua_transport_config_default(&transport_cfg);
363 transport_cfg.port = DEFAULT_RTP_PORT;
364
365 status = pjsua_media_transports_create(&transport_cfg);
Benny Prijonob90fd382011-09-18 14:59:56 +0000366 if (status != PJ_SUCCESS) {
367 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000368 return status;
Benny Prijonob90fd382011-09-18 14:59:56 +0000369 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000370 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000371#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000372
Benny Prijono0bc99a92011-03-17 04:34:43 +0000373 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000374 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000375
Benny Prijono9f468d12011-07-07 07:46:33 +0000376 /* Video */
377#if PJMEDIA_HAS_VIDEO
378 status = pjsua_vid_subsys_start();
Benny Prijonob90fd382011-09-18 14:59:56 +0000379 if (status != PJ_SUCCESS) {
380 pj_log_pop_indent();
Benny Prijono9f468d12011-07-07 07:46:33 +0000381 return status;
Benny Prijonob90fd382011-09-18 14:59:56 +0000382 }
Benny Prijono9f468d12011-07-07 07:46:33 +0000383#endif
384
Benny Prijonobf53b002010-01-04 13:08:31 +0000385 /* Perform NAT detection */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000386 status = pjsua_detect_nat_type();
387 if (status != PJ_SUCCESS) {
388 PJ_PERROR(1,(THIS_FILE, status, "NAT type detection failed"));
389 }
Benny Prijonobf53b002010-01-04 13:08:31 +0000390
Benny Prijonob90fd382011-09-18 14:59:56 +0000391 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000392 return PJ_SUCCESS;
393}
394
395
396/*
397 * Destroy pjsua media subsystem.
398 */
Sauw Minge7dbbc82011-10-24 09:28:13 +0000399pj_status_t pjsua_media_subsys_destroy(unsigned flags)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000400{
401 unsigned i;
402
Benny Prijono384dab42009-10-14 01:58:04 +0000403 PJ_LOG(4,(THIS_FILE, "Shutting down media.."));
Benny Prijonob90fd382011-09-18 14:59:56 +0000404 pj_log_push_indent();
Benny Prijono384dab42009-10-14 01:58:04 +0000405
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000406 close_snd_dev();
407
408 if (pjsua_var.mconf) {
409 pjmedia_conf_destroy(pjsua_var.mconf);
410 pjsua_var.mconf = NULL;
411 }
412
413 if (pjsua_var.null_port) {
414 pjmedia_port_destroy(pjsua_var.null_port);
415 pjsua_var.null_port = NULL;
416 }
417
418 /* Destroy file players */
419 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
420 if (pjsua_var.player[i].port) {
421 pjmedia_port_destroy(pjsua_var.player[i].port);
422 pjsua_var.player[i].port = NULL;
423 }
424 }
425
426 /* Destroy file recorders */
427 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
428 if (pjsua_var.recorder[i].port) {
429 pjmedia_port_destroy(pjsua_var.recorder[i].port);
430 pjsua_var.recorder[i].port = NULL;
431 }
432 }
433
434 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000435 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000436 unsigned strm_idx;
437 pjsua_call *call = &pjsua_var.calls[i];
438 for (strm_idx=0; strm_idx<call->med_cnt; ++strm_idx) {
439 pjsua_call_media *call_med = &call->media[strm_idx];
440 if (call_med->tp_st != PJSUA_MED_TP_IDLE) {
441 pjsua_media_channel_deinit(i);
442 }
443 if (call_med->tp && call_med->tp_auto_del) {
Sauw Minge7dbbc82011-10-24 09:28:13 +0000444 /* TODO: check if we're not allowed to send to network in the
445 * "flags", and if so do not do TURN allocation...
446 */
447 PJ_UNUSED_ARG(flags);
Benny Prijono0bc99a92011-03-17 04:34:43 +0000448 pjmedia_transport_close(call_med->tp);
449 }
450 call_med->tp = NULL;
Benny Prijono311b63f2008-07-14 11:31:40 +0000451 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000452 }
453
454 /* Destroy media endpoint. */
455 if (pjsua_var.med_endpt) {
456
Benny Prijono0bc99a92011-03-17 04:34:43 +0000457# if PJMEDIA_HAS_VIDEO
Benny Prijono9f468d12011-07-07 07:46:33 +0000458 pjsua_vid_subsys_destroy();
Benny Prijono0bc99a92011-03-17 04:34:43 +0000459# endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000460
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000461 pjmedia_endpt_destroy(pjsua_var.med_endpt);
462 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000463
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000464 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000465 // Not necessary, as pjmedia_snd_deinit() should have been called
466 // in pjmedia_endpt_destroy().
467 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000468 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000469
Benny Prijonode479562007-03-15 10:23:55 +0000470 /* Reset RTP port */
471 next_rtp_port = 0;
472
Benny Prijonob90fd382011-09-18 14:59:56 +0000473 pj_log_pop_indent();
474
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000475 return PJ_SUCCESS;
476}
477
Benny Prijono0bc99a92011-03-17 04:34:43 +0000478/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000479 * Create RTP and RTCP socket pair, and possibly resolve their public
480 * address via STUN.
481 */
482static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
483 pjmedia_sock_info *skinfo)
484{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000485 enum {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000486 RTP_RETRY = 100
487 };
488 int i;
489 pj_sockaddr_in bound_addr;
490 pj_sockaddr_in mapped_addr[2];
491 pj_status_t status = PJ_SUCCESS;
492 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
493 pj_sock_t sock[2];
494
495 /* Make sure STUN server resolution has completed */
496 status = resolve_stun_server(PJ_TRUE);
497 if (status != PJ_SUCCESS) {
498 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
499 return status;
500 }
501
502 if (next_rtp_port == 0)
503 next_rtp_port = (pj_uint16_t)cfg->port;
504
Benny Prijono0bc99a92011-03-17 04:34:43 +0000505 if (next_rtp_port == 0)
506 next_rtp_port = (pj_uint16_t)40000;
507
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000508 for (i=0; i<2; ++i)
509 sock[i] = PJ_INVALID_SOCKET;
510
511 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
512 if (cfg->bound_addr.slen) {
513 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
514 if (status != PJ_SUCCESS) {
515 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
516 status);
517 return status;
518 }
519 }
520
521 /* Loop retry to bind RTP and RTCP sockets. */
522 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
523
524 /* Create RTP socket. */
525 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
526 if (status != PJ_SUCCESS) {
527 pjsua_perror(THIS_FILE, "socket() error", status);
528 return status;
529 }
530
531 /* Apply QoS to RTP socket, if specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000532 status = pj_sock_apply_qos2(sock[0], cfg->qos_type,
533 &cfg->qos_params,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000534 2, THIS_FILE, "RTP socket");
535
536 /* Bind RTP socket */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000537 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000538 next_rtp_port);
539 if (status != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000540 pj_sock_close(sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000541 sock[0] = PJ_INVALID_SOCKET;
542 continue;
543 }
544
545 /* Create RTCP socket. */
546 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
547 if (status != PJ_SUCCESS) {
548 pjsua_perror(THIS_FILE, "socket() error", status);
549 pj_sock_close(sock[0]);
550 return status;
551 }
552
553 /* Apply QoS to RTCP socket, if specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000554 status = pj_sock_apply_qos2(sock[1], cfg->qos_type,
555 &cfg->qos_params,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000556 2, THIS_FILE, "RTCP socket");
557
558 /* Bind RTCP socket */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000559 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000560 (pj_uint16_t)(next_rtp_port+1));
561 if (status != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000562 pj_sock_close(sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000563 sock[0] = PJ_INVALID_SOCKET;
564
Benny Prijono0bc99a92011-03-17 04:34:43 +0000565 pj_sock_close(sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000566 sock[1] = PJ_INVALID_SOCKET;
567 continue;
568 }
569
570 /*
571 * If we're configured to use STUN, then find out the mapped address,
572 * and make sure that the mapped RTCP port is adjacent with the RTP.
573 */
574 if (pjsua_var.stun_srv.addr.sa_family != 0) {
575 char ip_addr[32];
576 pj_str_t stun_srv;
577
Benny Prijono0bc99a92011-03-17 04:34:43 +0000578 pj_ansi_strcpy(ip_addr,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000579 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
580 stun_srv = pj_str(ip_addr);
581
582 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
583 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
584 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
585 mapped_addr);
586 if (status != PJ_SUCCESS) {
587 pjsua_perror(THIS_FILE, "STUN resolve error", status);
588 goto on_error;
589 }
590
591#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijono0bc99a92011-03-17 04:34:43 +0000592 if (pj_ntohs(mapped_addr[1].sin_port) ==
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000593 pj_ntohs(mapped_addr[0].sin_port)+1)
594 {
595 /* Success! */
596 break;
597 }
598
Benny Prijono0bc99a92011-03-17 04:34:43 +0000599 pj_sock_close(sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000600 sock[0] = PJ_INVALID_SOCKET;
601
Benny Prijono0bc99a92011-03-17 04:34:43 +0000602 pj_sock_close(sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000603 sock[1] = PJ_INVALID_SOCKET;
604#else
Benny Prijono0bc99a92011-03-17 04:34:43 +0000605 if (pj_ntohs(mapped_addr[1].sin_port) !=
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000606 pj_ntohs(mapped_addr[0].sin_port)+1)
607 {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000608 PJ_LOG(4,(THIS_FILE,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000609 "Note: STUN mapped RTCP port %d is not adjacent"
610 " to RTP port %d",
611 pj_ntohs(mapped_addr[1].sin_port),
612 pj_ntohs(mapped_addr[0].sin_port)));
613 }
614 /* Success! */
615 break;
616#endif
617
618 } else if (cfg->public_addr.slen) {
619
620 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
621 (pj_uint16_t)next_rtp_port);
622 if (status != PJ_SUCCESS)
623 goto on_error;
624
625 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
626 (pj_uint16_t)(next_rtp_port+1));
627 if (status != PJ_SUCCESS)
628 goto on_error;
629
630 break;
631
632 } else {
633
634 if (bound_addr.sin_addr.s_addr == 0) {
635 pj_sockaddr addr;
636
637 /* Get local IP address. */
638 status = pj_gethostip(pj_AF_INET(), &addr);
639 if (status != PJ_SUCCESS)
640 goto on_error;
641
642 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
643 }
644
645 for (i=0; i<2; ++i) {
646 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
647 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
648 }
649
650 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
651 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
652 break;
653 }
654 }
655
656 if (sock[0] == PJ_INVALID_SOCKET) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000657 PJ_LOG(1,(THIS_FILE,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000658 "Unable to find appropriate RTP/RTCP ports combination"));
659 goto on_error;
660 }
661
662
663 skinfo->rtp_sock = sock[0];
Benny Prijono0bc99a92011-03-17 04:34:43 +0000664 pj_memcpy(&skinfo->rtp_addr_name,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000665 &mapped_addr[0], sizeof(pj_sockaddr_in));
666
667 skinfo->rtcp_sock = sock[1];
Benny Prijono0bc99a92011-03-17 04:34:43 +0000668 pj_memcpy(&skinfo->rtcp_addr_name,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000669 &mapped_addr[1], sizeof(pj_sockaddr_in));
670
671 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
672 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
673 sizeof(addr_buf), 3)));
674 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
675 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
676 sizeof(addr_buf), 3)));
677
678 next_rtp_port += 2;
679 return PJ_SUCCESS;
680
681on_error:
682 for (i=0; i<2; ++i) {
683 if (sock[i] != PJ_INVALID_SOCKET)
684 pj_sock_close(sock[i]);
685 }
686 return status;
687}
688
Benny Prijono0bc99a92011-03-17 04:34:43 +0000689/* Create normal UDP media transports */
690static pj_status_t create_udp_media_transport(const pjsua_transport_config *cfg,
691 pjsua_call_media *call_med)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000692{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000693 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000694 pj_status_t status;
695
Benny Prijono0bc99a92011-03-17 04:34:43 +0000696 status = create_rtp_rtcp_sock(cfg, &skinfo);
697 if (status != PJ_SUCCESS) {
698 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
699 status);
700 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000701 }
702
Benny Prijono0bc99a92011-03-17 04:34:43 +0000703 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
704 &skinfo, 0, &call_med->tp);
705 if (status != PJ_SUCCESS) {
706 pjsua_perror(THIS_FILE, "Unable to create media transport",
707 status);
708 goto on_error;
709 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000710
Benny Prijono0bc99a92011-03-17 04:34:43 +0000711 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
712 pjsua_var.media_cfg.tx_drop_pct);
713
714 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
715 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000716
Sauw Ming73ecfe82011-09-21 10:20:01 +0000717 call_med->tp_ready = PJ_SUCCESS;
718
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000719 return PJ_SUCCESS;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000720
721on_error:
722 if (call_med->tp)
723 pjmedia_transport_close(call_med->tp);
724
725 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000726}
727
Benny Prijono0bc99a92011-03-17 04:34:43 +0000728#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +0000729/* Create normal UDP media transports */
730static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000731{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000732 unsigned i;
733 pj_status_t status;
734
Benny Prijono0bc99a92011-03-17 04:34:43 +0000735 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
736 pjsua_call *call = &pjsua_var.calls[i];
737 unsigned strm_idx;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000738
Benny Prijono0bc99a92011-03-17 04:34:43 +0000739 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
740 pjsua_call_media *call_med = &call->media[strm_idx];
741
742 status = create_udp_media_transport(cfg, &call_med->tp);
743 if (status != PJ_SUCCESS)
744 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000745 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000746 }
747
Benny Prijonoc97608e2007-03-23 16:34:20 +0000748 return PJ_SUCCESS;
749
750on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000751 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
752 pjsua_call *call = &pjsua_var.calls[i];
753 unsigned strm_idx;
754
755 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
756 pjsua_call_media *call_med = &call->media[strm_idx];
757
758 if (call_med->tp) {
759 pjmedia_transport_close(call_med->tp);
760 call_med->tp = NULL;
761 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000762 }
763 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000764 return status;
765}
Benny Prijono0bc99a92011-03-17 04:34:43 +0000766#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000767
Benny Prijono096c56c2007-09-15 08:30:16 +0000768/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000769static void on_ice_complete(pjmedia_transport *tp,
770 pj_ice_strans_op op,
771 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000772{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000773 pjsua_call_media *call_med = (pjsua_call_media*)tp->user_data;
Benny Prijono096c56c2007-09-15 08:30:16 +0000774
Benny Prijono0bc99a92011-03-17 04:34:43 +0000775 if (!call_med)
Benny Prijonof76e1392008-06-06 14:51:48 +0000776 return;
777
778 switch (op) {
779 case PJ_ICE_STRANS_OP_INIT:
Sauw Mingc8e12942011-10-25 08:51:02 +0000780 PJSUA_LOCK();
Sauw Ming73ecfe82011-09-21 10:20:01 +0000781 call_med->tp_ready = result;
782 if (call_med->med_create_cb)
783 (*call_med->med_create_cb)(call_med, result,
784 call_med->call->secure_level, NULL);
Sauw Mingc8e12942011-10-25 08:51:02 +0000785 PJSUA_UNLOCK();
Benny Prijonof76e1392008-06-06 14:51:48 +0000786 break;
787 case PJ_ICE_STRANS_OP_NEGOTIATION:
788 if (result != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000789 call_med->state = PJSUA_CALL_MEDIA_ERROR;
790 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijonof76e1392008-06-06 14:51:48 +0000791
Benny Prijono0bc99a92011-03-17 04:34:43 +0000792 if (call_med->call && pjsua_var.ua_cfg.cb.on_call_media_state) {
793 pjsua_var.ua_cfg.cb.on_call_media_state(call_med->call->index);
Benny Prijonof76e1392008-06-06 14:51:48 +0000794 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000795 } else if (call_med->call) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000796 /* Send UPDATE if default transport address is different than
797 * what was advertised (ticket #881)
798 */
799 pjmedia_transport_info tpinfo;
800 pjmedia_ice_transport_info *ii = NULL;
801 unsigned i;
802
803 pjmedia_transport_info_init(&tpinfo);
804 pjmedia_transport_get_info(tp, &tpinfo);
805 for (i=0; i<tpinfo.specific_info_cnt; ++i) {
806 if (tpinfo.spc_info[i].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
807 ii = (pjmedia_ice_transport_info*)
808 tpinfo.spc_info[i].buffer;
809 break;
810 }
811 }
812
813 if (ii && ii->role==PJ_ICE_SESS_ROLE_CONTROLLING &&
814 pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000815 &call_med->rtp_addr))
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000816 {
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000817 pj_bool_t use_update;
818 const pj_str_t STR_UPDATE = { "UPDATE", 6 };
819 pjsip_dialog_cap_status support_update;
820 pjsip_dialog *dlg;
821
Benny Prijono0bc99a92011-03-17 04:34:43 +0000822 dlg = call_med->call->inv->dlg;
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000823 support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW,
824 NULL, &STR_UPDATE);
825 use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED);
826
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000827 PJ_LOG(4,(THIS_FILE,
828 "ICE default transport address has changed for "
Benny Prijonodb5d89d2011-10-25 13:39:06 +0000829 "call %d, sending %s",
830 call_med->call->index,
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000831 (use_update ? "UPDATE" : "re-INVITE")));
832
833 if (use_update)
Benny Prijono0bc99a92011-03-17 04:34:43 +0000834 pjsua_call_update(call_med->call->index, 0, NULL);
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000835 else
Benny Prijono0bc99a92011-03-17 04:34:43 +0000836 pjsua_call_reinvite(call_med->call->index, 0, NULL);
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000837 }
Benny Prijonof76e1392008-06-06 14:51:48 +0000838 }
839 break;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000840 case PJ_ICE_STRANS_OP_KEEP_ALIVE:
841 if (result != PJ_SUCCESS) {
842 PJ_PERROR(4,(THIS_FILE, result,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000843 "ICE keep alive failure for transport %d:%d",
844 call_med->call->index, call_med->idx));
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000845 }
Sauw Ming73ecfe82011-09-21 10:20:01 +0000846 if (pjsua_var.ua_cfg.cb.on_call_media_transport_state) {
847 pjsua_med_tp_state_info info;
848
849 pj_bzero(&info, sizeof(info));
850 info.med_idx = call_med->idx;
851 info.state = call_med->tp_st;
852 info.status = result;
853 info.ext_info = &op;
854 (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)(
855 call_med->call->index, &info);
856 }
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000857 if (pjsua_var.ua_cfg.cb.on_ice_transport_error) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000858 pjsua_call_id id = call_med->call->index;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000859 (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result,
860 NULL);
861 }
862 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000863 }
864}
865
866
Benny Prijonof76e1392008-06-06 14:51:48 +0000867/* Parse "HOST:PORT" format */
868static pj_status_t parse_host_port(const pj_str_t *host_port,
869 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000870{
Benny Prijonof76e1392008-06-06 14:51:48 +0000871 pj_str_t str_port;
872
873 str_port.ptr = pj_strchr(host_port, ':');
874 if (str_port.ptr != NULL) {
875 int iport;
876
877 host->ptr = host_port->ptr;
878 host->slen = (str_port.ptr - host->ptr);
879 str_port.ptr++;
880 str_port.slen = host_port->slen - host->slen - 1;
881 iport = (int)pj_strtoul(&str_port);
882 if (iport < 1 || iport > 65535)
883 return PJ_EINVAL;
884 *port = (pj_uint16_t)iport;
885 } else {
886 *host = *host_port;
887 *port = 0;
888 }
889
890 return PJ_SUCCESS;
891}
892
893/* Create ICE media transports (when ice is enabled) */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000894static pj_status_t create_ice_media_transport(
895 const pjsua_transport_config *cfg,
Sauw Ming73ecfe82011-09-21 10:20:01 +0000896 pjsua_call_media *call_med,
897 pj_bool_t async)
Benny Prijonof76e1392008-06-06 14:51:48 +0000898{
899 char stunip[PJ_INET6_ADDRSTRLEN];
900 pj_ice_strans_cfg ice_cfg;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000901 pjmedia_ice_cb ice_cb;
902 char name[32];
903 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000904 pj_status_t status;
905
Benny Prijonoda9785b2007-04-02 20:43:06 +0000906 /* Make sure STUN server resolution has completed */
Benny Prijonobb995fd2009-08-12 11:03:23 +0000907 status = resolve_stun_server(PJ_TRUE);
Benny Prijonoda9785b2007-04-02 20:43:06 +0000908 if (status != PJ_SUCCESS) {
909 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
910 return status;
911 }
912
Benny Prijonof76e1392008-06-06 14:51:48 +0000913 /* Create ICE stream transport configuration */
914 pj_ice_strans_cfg_default(&ice_cfg);
915 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
916 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
917 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
918
919 ice_cfg.af = pj_AF_INET();
920 ice_cfg.resolver = pjsua_var.resolver;
921
Benny Prijono329d6382009-05-29 13:04:03 +0000922 ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
923
Benny Prijonof76e1392008-06-06 14:51:48 +0000924 /* Configure STUN settings */
925 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
926 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
927 ice_cfg.stun.server = pj_str(stunip);
928 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
929 }
Benny Prijono329d6382009-05-29 13:04:03 +0000930 if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
931 ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
Benny Prijonof76e1392008-06-06 14:51:48 +0000932
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000933 /* Copy QoS setting to STUN setting */
934 ice_cfg.stun.cfg.qos_type = cfg->qos_type;
935 pj_memcpy(&ice_cfg.stun.cfg.qos_params, &cfg->qos_params,
936 sizeof(cfg->qos_params));
937
Benny Prijonof76e1392008-06-06 14:51:48 +0000938 /* Configure TURN settings */
939 if (pjsua_var.media_cfg.enable_turn) {
940 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
941 &ice_cfg.turn.server,
942 &ice_cfg.turn.port);
943 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
944 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
945 return PJ_EINVAL;
946 }
947 if (ice_cfg.turn.port == 0)
948 ice_cfg.turn.port = 3479;
949 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
950 pj_memcpy(&ice_cfg.turn.auth_cred,
951 &pjsua_var.media_cfg.turn_auth_cred,
952 sizeof(ice_cfg.turn.auth_cred));
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000953
954 /* Copy QoS setting to TURN setting */
955 ice_cfg.turn.cfg.qos_type = cfg->qos_type;
956 pj_memcpy(&ice_cfg.turn.cfg.qos_params, &cfg->qos_params,
957 sizeof(cfg->qos_params));
Benny Prijonof76e1392008-06-06 14:51:48 +0000958 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000959
Benny Prijono0bc99a92011-03-17 04:34:43 +0000960 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
961 ice_cb.on_ice_complete = &on_ice_complete;
962 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", call_med->idx);
963 call_med->tp_ready = PJ_EPENDING;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000964
Benny Prijono0bc99a92011-03-17 04:34:43 +0000965 comp_cnt = 1;
966 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
967 ++comp_cnt;
Benny Prijonof76e1392008-06-06 14:51:48 +0000968
Benny Prijonobd6613f2011-04-11 17:27:14 +0000969 status = pjmedia_ice_create3(pjsua_var.med_endpt, name, comp_cnt,
970 &ice_cfg, &ice_cb, 0, call_med,
971 &call_med->tp);
Benny Prijono0bc99a92011-03-17 04:34:43 +0000972 if (status != PJ_SUCCESS) {
973 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
974 status);
975 goto on_error;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000976 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000977
Benny Prijono0bc99a92011-03-17 04:34:43 +0000978 /* Wait until transport is initialized, or time out */
Sauw Ming73ecfe82011-09-21 10:20:01 +0000979 if (!async) {
980 PJSUA_UNLOCK();
981 while (call_med->tp_ready == PJ_EPENDING) {
982 pjsua_handle_events(100);
983 }
984 PJSUA_LOCK();
Benny Prijono0bc99a92011-03-17 04:34:43 +0000985 }
Sauw Ming73ecfe82011-09-21 10:20:01 +0000986
987 if (async && call_med->tp_ready == PJ_EPENDING) {
988 return PJ_EPENDING;
989 } else if (call_med->tp_ready != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000990 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
991 call_med->tp_ready);
992 status = call_med->tp_ready;
993 goto on_error;
994 }
995
996 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
997 pjsua_var.media_cfg.tx_drop_pct);
998
999 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
1000 pjsua_var.media_cfg.rx_drop_pct);
1001
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001002 return PJ_SUCCESS;
1003
1004on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +00001005 if (call_med->tp != NULL) {
1006 pjmedia_transport_close(call_med->tp);
1007 call_med->tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001008 }
1009
Benny Prijonoc97608e2007-03-23 16:34:20 +00001010 return status;
1011}
1012
Benny Prijono0bc99a92011-03-17 04:34:43 +00001013#if DISABLED_FOR_TICKET_1185
1014/* Create ICE media transports (when ice is enabled) */
1015static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
1016{
1017 unsigned i;
1018 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001019
Benny Prijono0bc99a92011-03-17 04:34:43 +00001020 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
1021 pjsua_call *call = &pjsua_var.calls[i];
1022 unsigned strm_idx;
1023
1024 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1025 pjsua_call_media *call_med = &call->media[strm_idx];
1026
1027 status = create_ice_media_transport(cfg, call_med);
1028 if (status != PJ_SUCCESS)
1029 goto on_error;
1030 }
1031 }
1032
1033 return PJ_SUCCESS;
1034
1035on_error:
1036 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
1037 pjsua_call *call = &pjsua_var.calls[i];
1038 unsigned strm_idx;
1039
1040 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1041 pjsua_call_media *call_med = &call->media[strm_idx];
1042
1043 if (call_med->tp) {
1044 pjmedia_transport_close(call_med->tp);
1045 call_med->tp = NULL;
1046 }
1047 }
1048 }
1049 return status;
1050}
1051#endif
1052
1053#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +00001054/*
Benny Prijono0bc99a92011-03-17 04:34:43 +00001055 * Create media transports for all the calls. This function creates
Benny Prijonoc97608e2007-03-23 16:34:20 +00001056 * one UDP media transport for each call.
1057 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001058PJ_DEF(pj_status_t) pjsua_media_transports_create(
1059 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001060{
1061 pjsua_transport_config cfg;
1062 unsigned i;
1063 pj_status_t status;
1064
1065
1066 /* Make sure pjsua_init() has been called */
1067 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
1068
1069 PJSUA_LOCK();
1070
1071 /* Delete existing media transports */
1072 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001073 pjsua_call *call = &pjsua_var.calls[i];
1074 unsigned strm_idx;
1075
1076 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1077 pjsua_call_media *call_med = &call->media[strm_idx];
1078
1079 if (call_med->tp && call_med->tp_auto_del) {
1080 pjmedia_transport_close(call_med->tp);
1081 call_med->tp = NULL;
1082 call_med->tp_orig = NULL;
1083 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001084 }
1085 }
1086
1087 /* Copy config */
1088 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
1089
Benny Prijono40860c32008-09-04 13:55:33 +00001090 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001091 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijono4d79b0f2009-10-25 09:02:07 +00001092 status = create_ice_media_transports(&cfg);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001093 } else {
1094 status = create_udp_media_transports(&cfg);
1095 }
1096
Benny Prijono40860c32008-09-04 13:55:33 +00001097 /* Set media transport auto_delete to True */
1098 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001099 pjsua_call *call = &pjsua_var.calls[i];
1100 unsigned strm_idx;
1101
1102 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1103 pjsua_call_media *call_med = &call->media[strm_idx];
1104
1105 call_med->tp_auto_del = PJ_TRUE;
1106 }
Benny Prijono40860c32008-09-04 13:55:33 +00001107 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001108
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001109 PJSUA_UNLOCK();
1110
1111 return status;
1112}
1113
Benny Prijono40860c32008-09-04 13:55:33 +00001114/*
1115 * Attach application's created media transports.
1116 */
1117PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
1118 unsigned count,
1119 pj_bool_t auto_delete)
1120{
1121 unsigned i;
1122
1123 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
1124
1125 /* Assign the media transports */
1126 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001127 pjsua_call *call = &pjsua_var.calls[i];
1128 unsigned strm_idx;
1129
1130 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1131 pjsua_call_media *call_med = &call->media[strm_idx];
1132
1133 if (call_med->tp && call_med->tp_auto_del) {
1134 pjmedia_transport_close(call_med->tp);
1135 call_med->tp = NULL;
1136 call_med->tp_orig = NULL;
1137 }
Benny Prijono40860c32008-09-04 13:55:33 +00001138 }
1139
Benny Prijono0bc99a92011-03-17 04:34:43 +00001140 PJ_TODO(remove_pjsua_media_transports_attach);
1141
1142 call->media[0].tp = tp[i].transport;
1143 call->media[0].tp_auto_del = auto_delete;
Benny Prijono40860c32008-09-04 13:55:33 +00001144 }
1145
1146 return PJ_SUCCESS;
1147}
Benny Prijono0bc99a92011-03-17 04:34:43 +00001148#endif
Benny Prijono40860c32008-09-04 13:55:33 +00001149
Benny Prijono0bc99a92011-03-17 04:34:43 +00001150/* Go through the list of media in the SDP, find acceptable media, and
1151 * sort them based on the "quality" of the media, and store the indexes
1152 * in the specified array. Media with the best quality will be listed
1153 * first in the array. The quality factors considered currently is
1154 * encryption.
1155 */
1156static void sort_media(const pjmedia_sdp_session *sdp,
1157 const pj_str_t *type,
1158 pjmedia_srtp_use use_srtp,
1159 pj_uint8_t midx[],
1160 unsigned *p_count)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001161{
1162 unsigned i;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001163 unsigned count = 0;
1164 int score[PJSUA_MAX_CALL_MEDIA];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001165
Benny Prijono0bc99a92011-03-17 04:34:43 +00001166 pj_assert(*p_count >= PJSUA_MAX_CALL_MEDIA);
1167
1168 *p_count = 0;
Benny Prijono09c0d672011-04-11 05:03:24 +00001169 for (i=0; i<PJSUA_MAX_CALL_MEDIA; ++i)
1170 score[i] = 1;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001171
1172 /* Score each media */
1173 for (i=0; i<sdp->media_count && count<PJSUA_MAX_CALL_MEDIA; ++i) {
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001174 const pjmedia_sdp_media *m = sdp->media[i];
Nanang Izzuddina6414292011-04-08 04:26:18 +00001175 const pjmedia_sdp_conn *c;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001176
Benny Prijono0bc99a92011-03-17 04:34:43 +00001177 /* Skip different media */
1178 if (pj_stricmp(&m->desc.media, type) != 0) {
1179 score[count++] = -22000;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001180 continue;
1181 }
1182
Nanang Izzuddina6414292011-04-08 04:26:18 +00001183 c = m->conn? m->conn : sdp->conn;
1184
Benny Prijono0bc99a92011-03-17 04:34:43 +00001185 /* Supported transports */
1186 if (pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0) {
1187 switch (use_srtp) {
1188 case PJMEDIA_SRTP_MANDATORY:
1189 case PJMEDIA_SRTP_OPTIONAL:
1190 ++score[i];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001191 break;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001192 case PJMEDIA_SRTP_DISABLED:
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001193 //--score[i];
1194 score[i] -= 5;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001195 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001196 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001197 } else if (pj_stricmp2(&m->desc.transport, "RTP/AVP")==0) {
1198 switch (use_srtp) {
1199 case PJMEDIA_SRTP_MANDATORY:
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001200 //--score[i];
1201 score[i] -= 5;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001202 break;
1203 case PJMEDIA_SRTP_OPTIONAL:
1204 /* No change in score */
1205 break;
1206 case PJMEDIA_SRTP_DISABLED:
1207 ++score[i];
1208 break;
1209 }
1210 } else {
1211 score[i] -= 10;
1212 }
1213
1214 /* Is media disabled? */
1215 if (m->desc.port == 0)
1216 score[i] -= 10;
1217
1218 /* Is media inactive? */
Nanang Izzuddina6414292011-04-08 04:26:18 +00001219 if (pjmedia_sdp_media_find_attr2(m, "inactive", NULL) ||
1220 pj_strcmp2(&c->addr, "0.0.0.0") == 0)
1221 {
1222 //score[i] -= 10;
1223 score[i] -= 1;
1224 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001225
1226 ++count;
1227 }
1228
1229 /* Created sorted list based on quality */
1230 for (i=0; i<count; ++i) {
1231 unsigned j;
1232 int best = 0;
1233
1234 for (j=1; j<count; ++j) {
1235 if (score[j] > score[best])
1236 best = j;
1237 }
1238 /* Don't put media with negative score, that media is unacceptable
1239 * for us.
1240 */
1241 if (score[best] >= 0) {
1242 midx[*p_count] = (pj_uint8_t)best;
1243 (*p_count)++;
1244 }
1245
1246 score[best] = -22000;
1247
1248 }
1249}
1250
Benny Prijonoee0ba182011-07-15 06:18:29 +00001251/* Callback to receive media events */
1252static pj_status_t call_media_on_event(pjmedia_event_subscription *esub,
1253 pjmedia_event *event)
1254{
1255 pjsua_call_media *call_med = (pjsua_call_media*)esub->user_data;
1256 pjsua_call *call = call_med->call;
1257
1258 if (pjsua_var.ua_cfg.cb.on_call_media_event && call) {
1259 ++event->proc_cnt;
1260 (*pjsua_var.ua_cfg.cb.on_call_media_event)(call->index,
1261 call_med->idx, event);
Benny Prijono53a7c702008-04-14 02:57:29 +00001262 }
1263
1264 return PJ_SUCCESS;
1265}
1266
Sauw Ming73ecfe82011-09-21 10:20:01 +00001267/* Set media transport state and notify the application via the callback. */
1268void set_media_tp_state(pjsua_call_media *call_med,
1269 pjsua_med_tp_st tp_st)
1270{
1271 if (pjsua_var.ua_cfg.cb.on_call_media_transport_state &&
1272 call_med->tp_st != tp_st)
1273 {
1274 pjsua_med_tp_state_info info;
1275
1276 pj_bzero(&info, sizeof(info));
1277 info.med_idx = call_med->idx;
1278 info.state = tp_st;
1279 info.status = call_med->tp_ready;
1280 (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)(
1281 call_med->call->index, &info);
1282 }
1283
1284 call_med->tp_st = tp_st;
1285}
1286
1287/* Callback to resume pjsua_call_media_init() after media transport
1288 * creation is completed.
1289 */
1290static pj_status_t call_media_init_cb(pjsua_call_media *call_med,
1291 pj_status_t status,
1292 int security_level,
1293 int *sip_err_code)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001294{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001295 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
Benny Prijonodb5d89d2011-10-25 13:39:06 +00001296 pjmedia_transport_info tpinfo;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001297 int err_code = 0;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001298
Sauw Ming73ecfe82011-09-21 10:20:01 +00001299 if (status != PJ_SUCCESS)
1300 goto on_error;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001301
Sauw Ming73ecfe82011-09-21 10:20:01 +00001302 if (call_med->tp_st == PJSUA_MED_TP_CREATING)
1303 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001304
Sauw Minge7dbbc82011-10-24 09:28:13 +00001305 if (!call_med->tp_orig &&
1306 pjsua_var.ua_cfg.cb.on_create_media_transport)
1307 {
1308 call_med->use_custom_med_tp = PJ_TRUE;
1309 } else
1310 call_med->use_custom_med_tp = PJ_FALSE;
1311
Benny Prijono0bc99a92011-03-17 04:34:43 +00001312#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1313 /* This function may be called when SRTP transport already exists
1314 * (e.g: in re-invite, update), don't need to destroy/re-create.
1315 */
Sauw Minge7dbbc82011-10-24 09:28:13 +00001316 if (!call_med->tp_orig) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001317 pjmedia_srtp_setting srtp_opt;
1318 pjmedia_transport *srtp = NULL;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001319
Benny Prijono0bc99a92011-03-17 04:34:43 +00001320 /* Check if SRTP requires secure signaling */
1321 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
1322 if (security_level < acc->cfg.srtp_secure_signaling) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001323 err_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001324 status = PJSIP_ESESSIONINSECURE;
1325 goto on_error;
1326 }
1327 }
1328
1329 /* Always create SRTP adapter */
1330 pjmedia_srtp_setting_default(&srtp_opt);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001331 srtp_opt.close_member_tp = PJ_TRUE;
Sauw Minge7dbbc82011-10-24 09:28:13 +00001332 /* If media session has been ever established, let's use remote's
Benny Prijono0bc99a92011-03-17 04:34:43 +00001333 * preference in SRTP usage policy, especially when it is stricter.
1334 */
1335 if (call_med->rem_srtp_use > acc->cfg.use_srtp)
1336 srtp_opt.use = call_med->rem_srtp_use;
1337 else
1338 srtp_opt.use = acc->cfg.use_srtp;
1339
1340 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
1341 call_med->tp,
1342 &srtp_opt, &srtp);
1343 if (status != PJ_SUCCESS) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001344 err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001345 goto on_error;
1346 }
1347
1348 /* Set SRTP as current media transport */
1349 call_med->tp_orig = call_med->tp;
1350 call_med->tp = srtp;
1351 }
1352#else
Benny Prijono7df19342011-07-23 02:54:03 +00001353 call_med->tp_orig = call_med->tp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001354 PJ_UNUSED_ARG(security_level);
1355#endif
1356
Benny Prijonodb5d89d2011-10-25 13:39:06 +00001357
1358 pjmedia_transport_info_init(&tpinfo);
1359 pjmedia_transport_get_info(call_med->tp, &tpinfo);
1360
1361 pj_sockaddr_cp(&call_med->rtp_addr, &tpinfo.sock_info.rtp_addr_name);
1362
1363
Benny Prijono0bc99a92011-03-17 04:34:43 +00001364on_error:
Sauw Ming73ecfe82011-09-21 10:20:01 +00001365 if (status != PJ_SUCCESS && call_med->tp) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001366 pjmedia_transport_close(call_med->tp);
1367 call_med->tp = NULL;
1368 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001369
1370 if (sip_err_code)
1371 *sip_err_code = err_code;
1372
1373 if (call_med->med_init_cb) {
1374 pjsua_med_tp_state_info info;
1375
1376 pj_bzero(&info, sizeof(info));
1377 info.status = status;
1378 info.state = call_med->tp_st;
1379 info.med_idx = call_med->idx;
1380 info.sip_err_code = err_code;
1381 (*call_med->med_init_cb)(call_med->call->index, &info);
1382 }
1383
1384 return status;
1385}
1386
1387/* Initialize the media line */
1388pj_status_t pjsua_call_media_init(pjsua_call_media *call_med,
1389 pjmedia_type type,
1390 const pjsua_transport_config *tcfg,
1391 int security_level,
1392 int *sip_err_code,
1393 pj_bool_t async,
1394 pjsua_med_tp_state_cb cb)
1395{
1396 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
1397 pj_status_t status = PJ_SUCCESS;
1398
1399 /*
1400 * Note: this function may be called when the media already exists
1401 * (e.g. in reinvites, updates, etc.)
1402 */
1403 call_med->type = type;
1404
1405 /* Create the media transport for initial call. */
1406 if (call_med->tp == NULL) {
1407#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
1408 /* While in initial call, set default video devices */
1409 if (type == PJMEDIA_TYPE_VIDEO) {
1410 call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev;
1411 call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev;
1412 if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) {
1413 pjmedia_vid_dev_info info;
1414 pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info);
1415 call_med->strm.v.rdr_dev = info.id;
1416 }
1417 if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
1418 pjmedia_vid_dev_info info;
1419 pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info);
1420 call_med->strm.v.cap_dev = info.id;
1421 }
Nanang Izzuddinc12a19d2011-10-03 05:23:59 +00001422
1423 /* Init event subscribtion */
1424 pjmedia_event_subscription_init(&call_med->esub_rend, &call_media_on_event,
1425 call_med);
1426 pjmedia_event_subscription_init(&call_med->esub_cap, &call_media_on_event,
1427 call_med);
Sauw Ming73ecfe82011-09-21 10:20:01 +00001428 }
1429#endif
1430
1431 set_media_tp_state(call_med, PJSUA_MED_TP_CREATING);
1432
Sauw Ming73ecfe82011-09-21 10:20:01 +00001433 if (pjsua_var.media_cfg.enable_ice) {
1434 status = create_ice_media_transport(tcfg, call_med, async);
Sauw Mingc8e12942011-10-25 08:51:02 +00001435 if (async && status == PJ_EPENDING) {
Sauw Ming848742f2011-09-28 04:20:30 +00001436 /* We will resume call media initialization in the
1437 * on_ice_complete() callback.
1438 */
Sauw Mingc8e12942011-10-25 08:51:02 +00001439 call_med->med_create_cb = &call_media_init_cb;
1440 call_med->med_init_cb = cb;
1441
Sauw Ming848742f2011-09-28 04:20:30 +00001442 return PJ_EPENDING;
1443 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001444 } else {
1445 status = create_udp_media_transport(tcfg, call_med);
1446 }
1447
Sauw Ming848742f2011-09-28 04:20:30 +00001448 if (status != PJ_SUCCESS) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001449 PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport"));
1450 return status;
1451 }
1452
1453 /* Media transport creation completed immediately, so
1454 * we don't need to call the callback.
1455 */
1456 call_med->med_init_cb = NULL;
1457
1458 } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) {
1459 /* Media is being reenabled. */
1460 set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
1461 }
1462
1463 return call_media_init_cb(call_med, status, security_level,
1464 sip_err_code);
1465}
1466
1467/* Callback to resume pjsua_media_channel_init() after media transport
1468 * initialization is completed.
1469 */
1470static pj_status_t media_channel_init_cb(pjsua_call_id call_id,
1471 const pjsua_med_tp_state_info *info)
1472{
1473 pjsua_call *call = &pjsua_var.calls[call_id];
1474 pj_status_t status = (info? info->status : PJ_SUCCESS);
1475 unsigned mi;
1476
1477 if (info) {
1478 pj_mutex_lock(call->med_ch_mutex);
1479
1480 /* Set the callback to NULL to indicate that the async operation
1481 * has completed.
1482 */
1483 call->media[info->med_idx].med_init_cb = NULL;
1484
1485 /* In case of failure, save the information to be returned
1486 * by the last media transport to finish.
1487 */
1488 if (info->status != PJ_SUCCESS)
1489 pj_memcpy(&call->med_ch_info, info, sizeof(info));
1490
1491 /* Check whether all the call's medias have finished calling their
1492 * callbacks.
1493 */
1494 for (mi=0; mi < call->med_cnt; ++mi) {
1495 pjsua_call_media *call_med = &call->media[mi];
1496
Sauw Ming3a55bb92011-10-06 06:49:09 +00001497 if (call_med->med_init_cb ||
1498 call_med->tp_st == PJSUA_MED_TP_NULL)
1499 {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001500 pj_mutex_unlock(call->med_ch_mutex);
1501 return PJ_SUCCESS;
1502 }
1503
1504 if (call_med->tp_ready != PJ_SUCCESS)
1505 status = call_med->tp_ready;
1506 }
1507
1508 /* OK, we are called by the last media transport finished. */
1509 pj_mutex_unlock(call->med_ch_mutex);
1510 }
1511
1512 if (call->med_ch_mutex) {
1513 pj_mutex_destroy(call->med_ch_mutex);
1514 call->med_ch_mutex = NULL;
1515 }
1516
1517 if (status != PJ_SUCCESS) {
1518 pjsua_media_channel_deinit(call_id);
1519 goto on_error;
1520 }
1521
1522 /* Tell the media transport of a new offer/answer session */
1523 for (mi=0; mi < call->med_cnt; ++mi) {
1524 pjsua_call_media *call_med = &call->media[mi];
1525
1526 /* Note: tp may be NULL if this media line is disabled */
1527 if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) {
Sauw Ming99cc8ff2011-09-22 04:24:56 +00001528 pj_pool_t *tmp_pool = call->async_call.pool_prov;
1529
1530 if (!tmp_pool) {
1531 tmp_pool = (call->inv? call->inv->pool_prov:
1532 call->async_call.dlg->pool);
1533 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001534
Sauw Minge7dbbc82011-10-24 09:28:13 +00001535 if (call_med->use_custom_med_tp) {
1536 unsigned custom_med_tp_flags = 0;
1537
1538 /* Use custom media transport returned by the application */
1539 call_med->tp =
1540 (*pjsua_var.ua_cfg.cb.on_create_media_transport)
1541 (call_id, mi, call_med->tp,
1542 custom_med_tp_flags);
1543 if (!call_med->tp) {
1544 status =
1545 PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
1546 }
1547 }
1548
1549 if (call_med->tp) {
1550 status = pjmedia_transport_media_create(
1551 call_med->tp, tmp_pool,
1552 0, call->async_call.rem_sdp, mi);
1553 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001554 if (status != PJ_SUCCESS) {
1555 call->med_ch_info.status = status;
1556 call->med_ch_info.med_idx = mi;
1557 call->med_ch_info.state = call_med->tp_st;
1558 call->med_ch_info.sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1559 pjsua_media_channel_deinit(call_id);
1560 goto on_error;
1561 }
1562
1563 set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
1564 }
1565 }
1566
1567 call->med_ch_info.status = PJ_SUCCESS;
1568
1569on_error:
1570 if (call->med_ch_cb)
1571 (*call->med_ch_cb)(call->index, &call->med_ch_info);
1572
Benny Prijono0bc99a92011-03-17 04:34:43 +00001573 return status;
1574}
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001575
Benny Prijonod8179652008-01-23 20:39:07 +00001576pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
1577 pjsip_role_e role,
1578 int security_level,
1579 pj_pool_t *tmp_pool,
1580 const pjmedia_sdp_session *rem_sdp,
Sauw Ming73ecfe82011-09-21 10:20:01 +00001581 int *sip_err_code,
1582 pj_bool_t async,
1583 pjsua_med_tp_state_cb cb)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001584{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001585 const pj_str_t STR_AUDIO = { "audio", 5 };
1586 const pj_str_t STR_VIDEO = { "video", 5 };
Benny Prijonod8179652008-01-23 20:39:07 +00001587 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijonod8179652008-01-23 20:39:07 +00001588 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001589 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1590 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
1591 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
1592 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
1593 pjmedia_type media_types[PJSUA_MAX_CALL_MEDIA];
1594 unsigned mi;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001595 pj_bool_t pending_med_tp = PJ_FALSE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001596 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001597
Benny Prijonod8179652008-01-23 20:39:07 +00001598 PJ_UNUSED_ARG(role);
1599
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001600 /*
1601 * Note: this function may be called when the media already exists
1602 * (e.g. in reinvites, updates, etc).
1603 */
1604
Benny Prijono0bc99a92011-03-17 04:34:43 +00001605 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
1606 return PJ_EBUSY;
1607
Sauw Ming73ecfe82011-09-21 10:20:01 +00001608 if (async) {
1609 pj_pool_t *tmppool = (call->inv? call->inv->pool_prov:
1610 call->async_call.dlg->pool);
1611
1612 status = pj_mutex_create_simple(tmppool, NULL, &call->med_ch_mutex);
1613 if (status != PJ_SUCCESS)
1614 return status;
1615 }
1616
Benny Prijonob90fd382011-09-18 14:59:56 +00001617 PJ_LOG(4,(THIS_FILE, "Call %d: initializing media..", call_id));
1618 pj_log_push_indent();
1619
Benny Prijono0bc99a92011-03-17 04:34:43 +00001620#if DISABLED_FOR_TICKET_1185
Benny Prijonod8179652008-01-23 20:39:07 +00001621 /* Return error if media transport has not been created yet
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001622 * (e.g. application is starting)
1623 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001624 for (i=0; i<call->med_cnt; ++i) {
1625 if (call->media[i].tp == NULL) {
Benny Prijonob90fd382011-09-18 14:59:56 +00001626 status = PJ_EBUSY;
1627 goto on_error;
Benny Prijonod8179652008-01-23 20:39:07 +00001628 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001629 }
Benny Prijonod8179652008-01-23 20:39:07 +00001630#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +00001631
Benny Prijono0bc99a92011-03-17 04:34:43 +00001632 if (rem_sdp) {
1633 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
1634 maudidx, &maudcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001635 // Don't apply media count limitation until SDP negotiation is done.
1636 //if (maudcnt > acc->cfg.max_audio_cnt)
1637 // maudcnt = acc->cfg.max_audio_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001638
Benny Prijono0bc99a92011-03-17 04:34:43 +00001639 if (maudcnt==0) {
1640 /* Expecting audio in the offer */
1641 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1642 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001643 status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
1644 goto on_error;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001645 }
1646
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001647#if PJMEDIA_HAS_VIDEO
Benny Prijono0bc99a92011-03-17 04:34:43 +00001648 sort_media(rem_sdp, &STR_VIDEO, acc->cfg.use_srtp,
1649 mvididx, &mvidcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001650 // Don't apply media count limitation until SDP negotiation is done.
1651 //if (mvidcnt > acc->cfg.max_video_cnt)
1652 //mvidcnt = acc->cfg.max_video_cnt;
1653#else
1654 mvidcnt = 0;
1655#endif
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001656
1657 /* Update media count only when remote add any media, this media count
1658 * must never decrease.
Benny Prijonod8179652008-01-23 20:39:07 +00001659 */
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001660 if (call->med_cnt < rem_sdp->media_count)
1661 call->med_cnt = PJ_MIN(rem_sdp->media_count, PJSUA_MAX_CALL_MEDIA);
Benny Prijonod8179652008-01-23 20:39:07 +00001662
Benny Prijono0bc99a92011-03-17 04:34:43 +00001663 } else {
1664 maudcnt = acc->cfg.max_audio_cnt;
1665 for (mi=0; mi<maudcnt; ++mi) {
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001666 maudidx[mi] = (pj_uint8_t)mi;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001667 media_types[mi] = PJMEDIA_TYPE_AUDIO;
Benny Prijonod8179652008-01-23 20:39:07 +00001668 }
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001669#if PJMEDIA_HAS_VIDEO
Benny Prijono0bc99a92011-03-17 04:34:43 +00001670 mvidcnt = acc->cfg.max_video_cnt;
1671 for (mi=0; mi<mvidcnt; ++mi) {
1672 media_types[maudcnt + mi] = PJMEDIA_TYPE_VIDEO;
1673 }
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001674#else
1675 mvidcnt = 0;
1676#endif
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001677 call->med_cnt = maudcnt + mvidcnt;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001678 }
1679
Benny Prijono0bc99a92011-03-17 04:34:43 +00001680 if (call->med_cnt == 0) {
1681 /* Expecting at least one media */
Benny Prijonoab8dba92008-06-27 21:59:15 +00001682 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001683 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001684 status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
1685 goto on_error;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001686 }
1687
Sauw Ming73ecfe82011-09-21 10:20:01 +00001688 if (async) {
1689 call->med_ch_cb = cb;
Benny Prijono7eff5ef2011-10-26 06:53:30 +00001690 }
1691
1692 if (rem_sdp) {
1693 call->async_call.rem_sdp =
1694 pjmedia_sdp_session_clone(call->inv->pool_prov, rem_sdp);
1695 } else {
1696 call->async_call.rem_sdp = NULL;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001697 }
1698
Sauw Ming99cc8ff2011-09-22 04:24:56 +00001699 call->async_call.pool_prov = tmp_pool;
1700
Benny Prijono0bc99a92011-03-17 04:34:43 +00001701 /* Initialize each media line */
1702 for (mi=0; mi < call->med_cnt; ++mi) {
1703 pjsua_call_media *call_med = &call->media[mi];
1704 pj_bool_t enabled = PJ_FALSE;
1705 pjmedia_type media_type = PJMEDIA_TYPE_NONE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001706
Benny Prijono0bc99a92011-03-17 04:34:43 +00001707 if (rem_sdp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001708 if (mi >= rem_sdp->media_count) {
1709 /* Media has been removed in remote re-offer */
1710 media_type = call_med->type;
1711 } else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_AUDIO)) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001712 media_type = PJMEDIA_TYPE_AUDIO;
1713 if (pj_memchr(maudidx, mi, maudcnt * sizeof(maudidx[0]))) {
1714 enabled = PJ_TRUE;
1715 }
1716 }
1717 else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_VIDEO)) {
1718 media_type = PJMEDIA_TYPE_VIDEO;
1719 if (pj_memchr(mvididx, mi, mvidcnt * sizeof(mvididx[0]))) {
1720 enabled = PJ_TRUE;
1721 }
1722 }
1723
1724 } else {
1725 enabled = PJ_TRUE;
1726 media_type = media_types[mi];
1727 }
1728
1729 if (enabled) {
1730 status = pjsua_call_media_init(call_med, media_type,
1731 &acc->cfg.rtp_cfg,
Sauw Ming73ecfe82011-09-21 10:20:01 +00001732 security_level, sip_err_code,
1733 async,
Sauw Ming3a55bb92011-10-06 06:49:09 +00001734 (async? &media_channel_init_cb:
1735 NULL));
Sauw Ming73ecfe82011-09-21 10:20:01 +00001736 if (status == PJ_EPENDING) {
1737 pending_med_tp = PJ_TRUE;
1738 } else if (status != PJ_SUCCESS) {
1739 if (pending_med_tp) {
1740 /* Save failure information. */
1741 call_med->tp_ready = status;
1742 pj_bzero(&call->med_ch_info, sizeof(call->med_ch_info));
1743 call->med_ch_info.status = status;
1744 call->med_ch_info.state = call_med->tp_st;
1745 call->med_ch_info.med_idx = call_med->idx;
1746 if (sip_err_code)
1747 call->med_ch_info.sip_err_code = *sip_err_code;
1748
1749 /* We will return failure in the callback later. */
1750 return PJ_EPENDING;
1751 }
1752
1753 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001754 goto on_error;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001755 }
1756 } else {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001757 /* By convention, the media is disabled if transport is NULL
1758 * or transport state is PJSUA_MED_TP_DISABLED.
1759 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001760 if (call_med->tp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001761 // Don't close transport here, as SDP negotiation has not been
1762 // done and stream may be still active.
1763 //pjmedia_transport_close(call_med->tp);
1764 //call_med->tp = NULL;
1765 pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT ||
1766 call_med->tp_st == PJSUA_MED_TP_RUNNING);
Sauw Ming73ecfe82011-09-21 10:20:01 +00001767 set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001768 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001769
1770 /* Put media type just for info */
1771 call_med->type = media_type;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001772 }
Benny Prijono224b4e22008-06-19 14:10:28 +00001773 }
1774
Benny Prijono0bc99a92011-03-17 04:34:43 +00001775 call->audio_idx = maudidx[0];
1776
1777 PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d",
1778 call->audio_idx, call->index));
1779
Sauw Ming73ecfe82011-09-21 10:20:01 +00001780 if (pending_med_tp) {
Sauw Ming99cc8ff2011-09-22 04:24:56 +00001781 /* We shouldn't use temporary pool anymore. */
1782 call->async_call.pool_prov = NULL;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001783 /* We have a pending media transport initialization. */
1784 pj_log_pop_indent();
1785 return PJ_EPENDING;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001786 }
1787
Sauw Ming73ecfe82011-09-21 10:20:01 +00001788 /* Media transport initialization completed immediately, so
1789 * we don't need to call the callback.
1790 */
1791 call->med_ch_cb = NULL;
1792
1793 status = media_channel_init_cb(call_id, NULL);
1794 if (status != PJ_SUCCESS && sip_err_code)
1795 *sip_err_code = call->med_ch_info.sip_err_code;
1796
Benny Prijonob90fd382011-09-18 14:59:56 +00001797 pj_log_pop_indent();
Sauw Ming73ecfe82011-09-21 10:20:01 +00001798 return status;
Benny Prijonob90fd382011-09-18 14:59:56 +00001799
1800on_error:
Sauw Ming73ecfe82011-09-21 10:20:01 +00001801 if (call->med_ch_mutex) {
1802 pj_mutex_destroy(call->med_ch_mutex);
1803 call->med_ch_mutex = NULL;
1804 }
1805
Benny Prijonob90fd382011-09-18 14:59:56 +00001806 pj_log_pop_indent();
1807 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001808}
1809
1810pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1811 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001812 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001813 pjmedia_sdp_session **p_sdp,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001814 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001815{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001816 enum { MAX_MEDIA = PJSUA_MAX_CALL_MEDIA };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001817 pjmedia_sdp_session *sdp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001818 pj_sockaddr origin;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001819 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001820 pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001821 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001822 pj_status_t status;
1823
Benny Prijono0bc99a92011-03-17 04:34:43 +00001824 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
Benny Prijono55e82352007-05-10 20:49:08 +00001825 return PJ_EBUSY;
Benny Prijono55e82352007-05-10 20:49:08 +00001826
Benny Prijono0bc99a92011-03-17 04:34:43 +00001827 if (rem_sdp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001828 /* If this is a re-offer, let's re-initialize media as remote may
1829 * add or remove media
1830 */
1831 if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) {
1832 status = pjsua_media_channel_init(call_id, PJSIP_ROLE_UAS,
1833 call->secure_level, pool,
Sauw Ming73ecfe82011-09-21 10:20:01 +00001834 rem_sdp, sip_err_code,
1835 PJ_FALSE, NULL);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001836 if (status != PJ_SUCCESS)
1837 return status;
Benny Prijono0324ba52010-12-02 10:41:46 +00001838 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001839
1840#if 0
1841 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001842 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1843 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001844
Benny Prijono0bc99a92011-03-17 04:34:43 +00001845 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
1846 maudidx, &maudcnt);
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001847
Benny Prijono0bc99a92011-03-17 04:34:43 +00001848 if (maudcnt==0) {
1849 /* Expecting audio in the offer */
1850 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1851 pjsua_media_channel_deinit(call_id);
1852 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijono224b4e22008-06-19 14:10:28 +00001853 }
Benny Prijono224b4e22008-06-19 14:10:28 +00001854
Benny Prijono0bc99a92011-03-17 04:34:43 +00001855 call->audio_idx = maudidx[0];
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001856#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00001857 } else {
1858 /* Audio is first in our offer, by convention */
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001859 // The audio_idx should not be changed here, as this function may be
1860 // called in generating re-offer and the current active audio index
1861 // can be anywhere.
1862 //call->audio_idx = 0;
Benny Prijono224b4e22008-06-19 14:10:28 +00001863 }
1864
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001865#if 0
1866 // Since r3512, old-style hold should have got transport, created by
1867 // pjsua_media_channel_init() in initial offer/answer or remote reoffer.
Benny Prijono224b4e22008-06-19 14:10:28 +00001868 /* Create media if it's not created. This could happen when call is
Benny Prijono0bc99a92011-03-17 04:34:43 +00001869 * currently on-hold (with the old style hold)
Benny Prijono224b4e22008-06-19 14:10:28 +00001870 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001871 if (call->media[call->audio_idx].tp == NULL) {
Benny Prijono224b4e22008-06-19 14:10:28 +00001872 pjsip_role_e role;
1873 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1874 status = pjsua_media_channel_init(call_id, role, call->secure_level,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001875 pool, rem_sdp, sip_err_code);
Benny Prijono224b4e22008-06-19 14:10:28 +00001876 if (status != PJ_SUCCESS)
1877 return status;
1878 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001879#endif
Benny Prijono224b4e22008-06-19 14:10:28 +00001880
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001881 /* Get SDP negotiator state */
1882 if (call->inv && call->inv->neg)
1883 sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg);
1884
Benny Prijono0bc99a92011-03-17 04:34:43 +00001885 /* Get one address to use in the origin field */
1886 pj_bzero(&origin, sizeof(origin));
1887 for (mi=0; mi<call->med_cnt; ++mi) {
1888 pjmedia_transport_info tpinfo;
Benny Prijono617c5bc2007-04-02 19:51:21 +00001889
Benny Prijono0bc99a92011-03-17 04:34:43 +00001890 if (call->media[mi].tp == NULL)
1891 continue;
1892
1893 pjmedia_transport_info_init(&tpinfo);
1894 pjmedia_transport_get_info(call->media[mi].tp, &tpinfo);
1895 pj_sockaddr_cp(&origin, &tpinfo.sock_info.rtp_addr_name);
1896 break;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001897 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001898
Benny Prijono0bc99a92011-03-17 04:34:43 +00001899 /* Create the base (blank) SDP */
1900 status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, pool, NULL,
1901 &origin, &sdp);
1902 if (status != PJ_SUCCESS)
1903 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001904
Benny Prijono0bc99a92011-03-17 04:34:43 +00001905 /* Process each media line */
1906 for (mi=0; mi<call->med_cnt; ++mi) {
1907 pjsua_call_media *call_med = &call->media[mi];
1908 pjmedia_sdp_media *m = NULL;
1909 pjmedia_transport_info tpinfo;
1910
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001911 if (rem_sdp && mi >= rem_sdp->media_count) {
1912 /* Remote might have removed some media lines. */
1913 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001914 }
1915
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001916 if (call_med->tp == NULL || call_med->tp_st == PJSUA_MED_TP_DISABLED)
1917 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001918 /*
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001919 * This media is disabled. Just create a valid SDP with zero
Benny Prijono0bc99a92011-03-17 04:34:43 +00001920 * port.
1921 */
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001922 if (rem_sdp) {
1923 /* Just clone the remote media and deactivate it */
1924 m = pjmedia_sdp_media_clone_deactivate(pool,
1925 rem_sdp->media[mi]);
1926 } else {
1927 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1928 m->desc.transport = pj_str("RTP/AVP");
1929 m->desc.fmt_count = 1;
1930 m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
1931 m->conn->net_type = pj_str("IN");
1932 m->conn->addr_type = pj_str("IP4");
1933 m->conn->addr = pj_str("127.0.0.1");
Benny Prijonoa310bd22008-06-27 21:19:44 +00001934
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001935 switch (call_med->type) {
1936 case PJMEDIA_TYPE_AUDIO:
1937 m->desc.media = pj_str("audio");
1938 m->desc.fmt[0] = pj_str("0");
1939 break;
1940 case PJMEDIA_TYPE_VIDEO:
1941 m->desc.media = pj_str("video");
1942 m->desc.fmt[0] = pj_str("31");
1943 break;
1944 default:
1945 if (rem_sdp) {
1946 pj_strdup(pool, &m->desc.media,
1947 &rem_sdp->media[mi]->desc.media);
1948 pj_strdup(pool, &m->desc.fmt[0],
1949 &rem_sdp->media[mi]->desc.fmt[0]);
1950 } else {
1951 pj_assert(!"Invalid call_med media type");
1952 return PJ_EBUG;
1953 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001954 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001955 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001956
1957 sdp->media[sdp->media_count++] = m;
1958 continue;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001959 }
1960
Benny Prijono0bc99a92011-03-17 04:34:43 +00001961 /* Get transport address info */
1962 pjmedia_transport_info_init(&tpinfo);
1963 pjmedia_transport_get_info(call_med->tp, &tpinfo);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001964
Benny Prijono0bc99a92011-03-17 04:34:43 +00001965 /* Ask pjmedia endpoint to create SDP media line */
1966 switch (call_med->type) {
1967 case PJMEDIA_TYPE_AUDIO:
1968 status = pjmedia_endpt_create_audio_sdp(pjsua_var.med_endpt, pool,
1969 &tpinfo.sock_info, 0, &m);
1970 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00001971#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00001972 case PJMEDIA_TYPE_VIDEO:
1973 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
1974 &tpinfo.sock_info, 0, &m);
1975 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00001976#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00001977 default:
1978 pj_assert(!"Invalid call_med media type");
1979 return PJ_EBUG;
1980 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001981
Benny Prijono0bc99a92011-03-17 04:34:43 +00001982 if (status != PJ_SUCCESS)
1983 return status;
1984
1985 sdp->media[sdp->media_count++] = m;
1986
1987 /* Give to transport */
1988 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
1989 sdp, rem_sdp, mi);
1990 if (status != PJ_SUCCESS) {
1991 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1992 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001993 }
Nanang Izzuddin91ba2a22011-04-11 19:59:09 +00001994
1995 /* Copy c= line of the first media to session level,
1996 * if there's none.
1997 */
1998 if (sdp->conn == NULL) {
1999 sdp->conn = pjmedia_sdp_conn_clone(pool, m->conn);
Benny Prijonoa310bd22008-06-27 21:19:44 +00002000 }
2001 }
2002
Benny Prijono6ba8c542007-10-16 01:34:14 +00002003 /* Add NAT info in the SDP */
2004 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
2005 pjmedia_sdp_attr *a;
2006 pj_str_t value;
2007 char nat_info[80];
2008
2009 value.ptr = nat_info;
2010 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
2011 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
2012 "%d", pjsua_var.nat_type);
2013 } else {
2014 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
2015 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
2016 "%d %s",
2017 pjsua_var.nat_type,
2018 type_name);
2019 }
2020
2021 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
2022
2023 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
2024
2025 }
2026
Benny Prijonoc97608e2007-03-23 16:34:20 +00002027
Benny Prijono0bc99a92011-03-17 04:34:43 +00002028#if DISABLED_FOR_TICKET_1185 && defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002029 /* Check if SRTP is in optional mode and configured to use duplicated
2030 * media, i.e: secured and unsecured version, in the SDP offer.
2031 */
2032 if (!rem_sdp &&
2033 pjsua_var.acc[call->acc_id].cfg.use_srtp == PJMEDIA_SRTP_OPTIONAL &&
2034 pjsua_var.acc[call->acc_id].cfg.srtp_optional_dup_offer)
2035 {
2036 unsigned i;
2037
2038 for (i = 0; i < sdp->media_count; ++i) {
2039 pjmedia_sdp_media *m = sdp->media[i];
2040
Benny Prijono0bc99a92011-03-17 04:34:43 +00002041 /* Check if this media is unsecured but has SDP "crypto"
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002042 * attribute.
2043 */
2044 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") == 0 &&
2045 pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL)
2046 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002047 if (i == (unsigned)call->audio_idx &&
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002048 sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE)
2049 {
2050 /* This is a session update, and peer has chosen the
2051 * unsecured version, so let's make this unsecured too.
2052 */
2053 pjmedia_sdp_media_remove_all_attr(m, "crypto");
2054 } else {
2055 /* This is new offer, duplicate media so we'll have
2056 * secured (with "RTP/SAVP" transport) and and unsecured
2057 * versions.
2058 */
2059 pjmedia_sdp_media *new_m;
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002060
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002061 /* Duplicate this media and apply secured transport */
2062 new_m = pjmedia_sdp_media_clone(pool, m);
2063 pj_strdup2(pool, &new_m->desc.transport, "RTP/SAVP");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002064
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002065 /* Remove the "crypto" attribute in the unsecured media */
2066 pjmedia_sdp_media_remove_all_attr(m, "crypto");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002067
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002068 /* Insert the new media before the unsecured media */
2069 if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002070 pj_array_insert(sdp->media, sizeof(new_m),
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002071 sdp->media_count, i, &new_m);
2072 ++sdp->media_count;
2073 ++i;
2074 }
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002075 }
2076 }
2077 }
2078 }
2079#endif
2080
Benny Prijonoc97608e2007-03-23 16:34:20 +00002081 *p_sdp = sdp;
2082 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002083}
2084
2085
2086static void stop_media_session(pjsua_call_id call_id)
2087{
2088 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002089 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002090
Benny Prijonob90fd382011-09-18 14:59:56 +00002091 pj_log_push_indent();
2092
Benny Prijono0bc99a92011-03-17 04:34:43 +00002093 for (mi=0; mi<call->med_cnt; ++mi) {
2094 pjsua_call_media *call_med = &call->media[mi];
2095
2096 if (call_med->type == PJMEDIA_TYPE_AUDIO) {
2097 pjmedia_stream *strm = call_med->strm.a.stream;
2098 pjmedia_rtcp_stat stat;
2099
2100 if (strm) {
2101 if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) {
2102 if (pjsua_var.mconf) {
2103 pjsua_conf_remove_port(call_med->strm.a.conf_slot);
2104 }
2105 call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
2106 }
2107
2108 if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
2109 (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS))
2110 {
2111 /* Save RTP timestamp & sequence, so when media session is
2112 * restarted, those values will be restored as the initial
2113 * RTP timestamp & sequence of the new media session. So in
2114 * the same call session, RTP timestamp and sequence are
2115 * guaranteed to be contigue.
2116 */
2117 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
2118 call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
2119 call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
2120 }
2121
2122 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
2123 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, strm, mi);
2124 }
2125
2126 pjmedia_stream_destroy(strm);
2127 call_med->strm.a.stream = NULL;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002128 }
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00002129 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002130
2131#if PJMEDIA_HAS_VIDEO
2132 else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
Nanang Izzuddin62053a62011-07-12 11:08:32 +00002133 stop_video_stream(call_med);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002134 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002135#endif
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002136
2137 PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed",
2138 call_id, mi));
Sauw Minge7dbbc82011-10-24 09:28:13 +00002139 call_med->prev_state = call_med->state;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002140 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002141 }
Benny Prijonob90fd382011-09-18 14:59:56 +00002142
2143 pj_log_pop_indent();
Benny Prijonoc97608e2007-03-23 16:34:20 +00002144}
2145
2146pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
2147{
2148 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002149 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002150
Sauw Ming903154f2011-10-03 08:22:48 +00002151 PJSUA_LOCK();
Sauw Mingec765352011-10-03 02:04:36 +00002152 for (mi=0; mi<call->med_cnt; ++mi) {
2153 pjsua_call_media *call_med = &call->media[mi];
2154
Sauw Ming903154f2011-10-03 08:22:48 +00002155 if (call_med->tp_st == PJSUA_MED_TP_CREATING) {
2156 /* We will do the deinitialization after media transport
2157 * creation is completed.
2158 */
2159 call->async_call.med_ch_deinit = PJ_TRUE;
2160 PJSUA_UNLOCK();
2161 return PJ_SUCCESS;
2162 }
Sauw Mingec765352011-10-03 02:04:36 +00002163 }
Sauw Ming903154f2011-10-03 08:22:48 +00002164 PJSUA_UNLOCK();
Sauw Mingec765352011-10-03 02:04:36 +00002165
Benny Prijonob90fd382011-09-18 14:59:56 +00002166 PJ_LOG(4,(THIS_FILE, "Call %d: deinitializing media..", call_id));
2167 pj_log_push_indent();
2168
Sauw Minge7dbbc82011-10-24 09:28:13 +00002169 for (mi=0; mi<call->med_cnt; ++mi) {
2170 pjsua_call_media *call_med = &call->media[mi];
2171
2172 if (call_med->type == PJMEDIA_TYPE_AUDIO && call_med->strm.a.stream)
2173 pjmedia_stream_send_rtcp_bye(call_med->strm.a.stream);
2174 }
2175
Benny Prijonoc97608e2007-03-23 16:34:20 +00002176 stop_media_session(call_id);
2177
Benny Prijono0bc99a92011-03-17 04:34:43 +00002178 for (mi=0; mi<call->med_cnt; ++mi) {
2179 pjsua_call_media *call_med = &call->media[mi];
Benny Prijonoc97608e2007-03-23 16:34:20 +00002180
Sauw Minge7dbbc82011-10-24 09:28:13 +00002181 if (call_med->tp_st > PJSUA_MED_TP_IDLE) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002182 pjmedia_transport_media_stop(call_med->tp);
Sauw Ming73ecfe82011-09-21 10:20:01 +00002183 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002184 }
2185
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002186 //if (call_med->tp_orig && call_med->tp &&
2187 // call_med->tp != call_med->tp_orig)
2188 //{
2189 // pjmedia_transport_close(call_med->tp);
2190 // call_med->tp = call_med->tp_orig;
2191 //}
2192 if (call_med->tp) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002193 pjmedia_transport_close(call_med->tp);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002194 call_med->tp = call_med->tp_orig = NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002195 }
Sauw Minge7dbbc82011-10-24 09:28:13 +00002196 call_med->tp_orig = NULL;
Benny Prijonod8179652008-01-23 20:39:07 +00002197 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00002198
2199 check_snd_dev_idle();
Benny Prijonob90fd382011-09-18 14:59:56 +00002200 pj_log_pop_indent();
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00002201
Benny Prijonoc97608e2007-03-23 16:34:20 +00002202 return PJ_SUCCESS;
2203}
2204
2205
2206/*
2207 * DTMF callback from the stream.
2208 */
2209static void dtmf_callback(pjmedia_stream *strm, void *user_data,
2210 int digit)
2211{
2212 PJ_UNUSED_ARG(strm);
2213
Benny Prijonob90fd382011-09-18 14:59:56 +00002214 pj_log_push_indent();
2215
Benny Prijono0c068262008-02-14 14:38:52 +00002216 /* For discussions about call mutex protection related to this
2217 * callback, please see ticket #460:
2218 * http://trac.pjsip.org/repos/ticket/460#comment:4
2219 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00002220 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
2221 pjsua_call_id call_id;
2222
Benny Prijonod8179652008-01-23 20:39:07 +00002223 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002224 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
2225 }
Benny Prijonob90fd382011-09-18 14:59:56 +00002226
2227 pj_log_pop_indent();
Benny Prijonoc97608e2007-03-23 16:34:20 +00002228}
2229
2230
Benny Prijono0bc99a92011-03-17 04:34:43 +00002231static pj_status_t audio_channel_update(pjsua_call_media *call_med,
2232 pj_pool_t *tmp_pool,
2233 const pjmedia_sdp_session *local_sdp,
2234 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00002235{
Benny Prijono0bc99a92011-03-17 04:34:43 +00002236 pjsua_call *call = call_med->call;
2237 pjmedia_stream_info the_si, *si = &the_si;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002238 pjmedia_port *media_port;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002239 unsigned strm_idx = call_med->idx;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002240 pj_status_t status;
Benny Prijonob90fd382011-09-18 14:59:56 +00002241
2242 PJ_LOG(4,(THIS_FILE,"Audio channel update.."));
2243 pj_log_push_indent();
Benny Prijono0bc99a92011-03-17 04:34:43 +00002244
2245 status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
2246 local_sdp, remote_sdp, strm_idx);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002247 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002248 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002249
Sauw Minge7dbbc82011-10-24 09:28:13 +00002250 si->rtcp_sdes_bye_disabled = PJ_TRUE;
2251
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002252 /* Check if no media is active */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002253 if (si->dir == PJMEDIA_DIR_NONE) {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002254 /* Call media state */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002255 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002256
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002257 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002258 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijono68f9e4f2008-03-21 08:56:02 +00002259
Benny Prijonoc97608e2007-03-23 16:34:20 +00002260 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002261 pjmedia_transport_info tp_info;
2262
Benny Prijono224b4e22008-06-19 14:10:28 +00002263 /* Start/restart media transport */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002264 status = pjmedia_transport_media_start(call_med->tp,
2265 tmp_pool, local_sdp,
2266 remote_sdp, strm_idx);
Benny Prijonod8179652008-01-23 20:39:07 +00002267 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002268 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002269
Sauw Ming73ecfe82011-09-21 10:20:01 +00002270 set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING);
Benny Prijono224b4e22008-06-19 14:10:28 +00002271
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002272 /* Get remote SRTP usage policy */
2273 pjmedia_transport_info_init(&tp_info);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002274 pjmedia_transport_get_info(call_med->tp, &tp_info);
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002275 if (tp_info.specific_info_cnt > 0) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +00002276 unsigned i;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002277 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
2278 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
2279 {
2280 pjmedia_srtp_info *srtp_info =
2281 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
2282
Benny Prijono0bc99a92011-03-17 04:34:43 +00002283 call_med->rem_srtp_use = srtp_info->peer_use;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002284 break;
2285 }
2286 }
2287 }
2288
Benny Prijonoc97608e2007-03-23 16:34:20 +00002289 /* Override ptime, if this option is specified. */
2290 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00002291 si->param->setting.frm_per_pkt = (pj_uint8_t)
2292 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
2293 if (si->param->setting.frm_per_pkt == 0)
2294 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002295 }
2296
2297 /* Disable VAD, if this option is specified. */
2298 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00002299 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002300 }
2301
2302
2303 /* Optionally, application may modify other stream settings here
2304 * (such as jitter buffer parameters, codec ptime, etc.)
2305 */
Benny Prijono91e567e2007-12-28 08:51:58 +00002306 si->jb_init = pjsua_var.media_cfg.jb_init;
2307 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
2308 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
2309 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002310
Benny Prijono8147f402007-11-21 14:50:07 +00002311 /* Set SSRC */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002312 si->ssrc = call_med->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00002313
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002314 /* Set RTP timestamp & sequence, normally these value are intialized
2315 * automatically when stream session created, but for some cases (e.g:
2316 * call reinvite, call update) timestamp and sequence need to be kept
2317 * contigue.
2318 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002319 si->rtp_ts = call_med->rtp_tx_ts;
2320 si->rtp_seq = call_med->rtp_tx_seq;
2321 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002322
Nanang Izzuddin5e39a2b2010-09-20 06:13:02 +00002323#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
2324 /* Enable/disable stream keep-alive and NAT hole punch. */
2325 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
2326#endif
2327
Benny Prijonoc97608e2007-03-23 16:34:20 +00002328 /* Create session based on session info. */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002329 status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,
2330 call_med->tp, NULL,
2331 &call_med->strm.a.stream);
2332 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002333 goto on_return;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002334 }
2335
2336 /* Start stream */
2337 status = pjmedia_stream_start(call_med->strm.a.stream);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002338 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002339 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002340 }
2341
Sauw Minge7dbbc82011-10-24 09:28:13 +00002342 if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE)
2343 pjmedia_stream_send_rtcp_sdes(call_med->strm.a.stream);
2344
Benny Prijonoc97608e2007-03-23 16:34:20 +00002345 /* If DTMF callback is installed by application, install our
2346 * callback to the session.
2347 */
2348 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002349 pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
2350 &dtmf_callback,
2351 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00002352 }
2353
2354 /* Get the port interface of the first stream in the session.
2355 * We need the port interface to add to the conference bridge.
2356 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002357 pjmedia_stream_get_port(call_med->strm.a.stream, &media_port);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002358
Benny Prijonofc13bf62008-02-20 08:56:15 +00002359 /* Notify application about stream creation.
2360 * Note: application may modify media_port to point to different
2361 * media port
2362 */
2363 if (pjsua_var.ua_cfg.cb.on_stream_created) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002364 pjsua_var.ua_cfg.cb.on_stream_created(call->index,
2365 call_med->strm.a.stream,
2366 strm_idx, &media_port);
Benny Prijonofc13bf62008-02-20 08:56:15 +00002367 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002368
2369 /*
2370 * Add the call to conference bridge.
2371 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002372 {
2373 char tmp[PJSIP_MAX_URL_SIZE];
2374 pj_str_t port_name;
2375
2376 port_name.ptr = tmp;
2377 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
2378 call->inv->dlg->remote.info->uri,
2379 tmp, sizeof(tmp));
2380 if (port_name.slen < 1) {
2381 port_name = pj_str("call");
2382 }
Benny Prijono40d62b62009-08-12 17:53:47 +00002383 status = pjmedia_conf_add_port( pjsua_var.mconf,
2384 call->inv->pool_prov,
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002385 media_port,
2386 &port_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +00002387 (unsigned*)
2388 &call_med->strm.a.conf_slot);
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002389 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002390 goto on_return;
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002391 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002392 }
2393
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002394 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002395 call_med->dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002396
2397 /* Call media state */
2398 if (call->local_hold)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002399 call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
2400 else if (call_med->dir == PJMEDIA_DIR_DECODING)
2401 call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002402 else
Benny Prijono0bc99a92011-03-17 04:34:43 +00002403 call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002404 }
2405
2406 /* Print info. */
2407 {
2408 char info[80];
2409 int info_len = 0;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002410 int len;
2411 const char *dir;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002412
Benny Prijono0bc99a92011-03-17 04:34:43 +00002413 switch (si->dir) {
2414 case PJMEDIA_DIR_NONE:
2415 dir = "inactive";
2416 break;
2417 case PJMEDIA_DIR_ENCODING:
2418 dir = "sendonly";
2419 break;
2420 case PJMEDIA_DIR_DECODING:
2421 dir = "recvonly";
2422 break;
2423 case PJMEDIA_DIR_ENCODING_DECODING:
2424 dir = "sendrecv";
2425 break;
2426 default:
2427 dir = "unknown";
2428 break;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002429 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00002430 len = pj_ansi_sprintf( info+info_len,
2431 ", stream #%d: %.*s (%s)", strm_idx,
2432 (int)si->fmt.encoding_name.slen,
2433 si->fmt.encoding_name.ptr,
2434 dir);
2435 if (len > 0)
2436 info_len += len;
Benny Prijonob90fd382011-09-18 14:59:56 +00002437 PJ_LOG(4,(THIS_FILE,"Audio updated%s", info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00002438 }
2439
Benny Prijonob90fd382011-09-18 14:59:56 +00002440on_return:
2441 pj_log_pop_indent();
2442 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002443}
2444
Benny Prijono0bc99a92011-03-17 04:34:43 +00002445pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
2446 const pjmedia_sdp_session *local_sdp,
2447 const pjmedia_sdp_session *remote_sdp)
2448{
2449 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002450 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002451 pj_pool_t *tmp_pool = call->inv->pool_prov;
2452 unsigned mi;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002453 pj_bool_t got_media = PJ_FALSE;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002454 pj_status_t status = PJ_SUCCESS;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002455
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002456 const pj_str_t STR_AUDIO = { "audio", 5 };
2457 const pj_str_t STR_VIDEO = { "video", 5 };
2458 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
2459 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
2460 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
2461 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
2462 pj_bool_t need_renego_sdp = PJ_FALSE;
2463
Benny Prijono0bc99a92011-03-17 04:34:43 +00002464 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
2465 return PJ_EBUSY;
2466
Benny Prijonob90fd382011-09-18 14:59:56 +00002467 PJ_LOG(4,(THIS_FILE, "Call %d: updating media..", call_id));
2468 pj_log_push_indent();
2469
Benny Prijono0bc99a92011-03-17 04:34:43 +00002470 /* Destroy existing media session, if any. */
2471 stop_media_session(call->index);
2472
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002473 /* Call media count must be at least equal to SDP media. Note that
2474 * it may not be equal when remote removed any SDP media line.
2475 */
2476 pj_assert(call->med_cnt >= local_sdp->media_count);
2477
Benny Prijono0bc99a92011-03-17 04:34:43 +00002478 /* Reset audio_idx first */
2479 call->audio_idx = -1;
2480
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002481 /* Apply maximum audio/video count of the account */
2482 sort_media(local_sdp, &STR_AUDIO, acc->cfg.use_srtp,
2483 maudidx, &maudcnt);
2484#if PJMEDIA_HAS_VIDEO
2485 sort_media(local_sdp, &STR_VIDEO, acc->cfg.use_srtp,
2486 mvididx, &mvidcnt);
2487#else
2488 PJ_UNUSED_ARG(STR_VIDEO);
2489 mvidcnt = 0;
2490#endif
2491 if (maudcnt > acc->cfg.max_audio_cnt || mvidcnt > acc->cfg.max_video_cnt)
2492 {
2493 pjmedia_sdp_session *local_sdp2;
2494
2495 maudcnt = PJ_MIN(maudcnt, acc->cfg.max_audio_cnt);
2496 mvidcnt = PJ_MIN(mvidcnt, acc->cfg.max_video_cnt);
2497 local_sdp2 = pjmedia_sdp_session_clone(tmp_pool, local_sdp);
2498
2499 for (mi=0; mi < local_sdp2->media_count; ++mi) {
2500 pjmedia_sdp_media *m = local_sdp2->media[mi];
2501
2502 if (m->desc.port == 0 ||
2503 pj_memchr(maudidx, mi, maudcnt*sizeof(maudidx[0])) ||
2504 pj_memchr(mvididx, mi, mvidcnt*sizeof(mvididx[0])))
2505 {
2506 continue;
2507 }
2508
2509 /* Deactivate this media */
2510 pjmedia_sdp_media_deactivate(tmp_pool, m);
2511 }
2512
2513 local_sdp = local_sdp2;
2514 need_renego_sdp = PJ_TRUE;
2515 }
2516
Benny Prijono0bc99a92011-03-17 04:34:43 +00002517 /* Process each media stream */
2518 for (mi=0; mi < call->med_cnt; ++mi) {
2519 pjsua_call_media *call_med = &call->media[mi];
2520
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002521 if (mi >= local_sdp->media_count ||
2522 mi >= remote_sdp->media_count)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002523 {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002524 /* This may happen when remote removed any SDP media lines in
2525 * its re-offer.
2526 */
2527 continue;
2528#if 0
Benny Prijono0bc99a92011-03-17 04:34:43 +00002529 /* Something is wrong */
2530 PJ_LOG(1,(THIS_FILE, "Error updating media for call %d: "
2531 "invalid media index %d in SDP", call_id, mi));
Benny Prijonob90fd382011-09-18 14:59:56 +00002532 status = PJMEDIA_SDP_EINSDP;
2533 goto on_error;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002534#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002535 }
2536
2537 switch (call_med->type) {
2538 case PJMEDIA_TYPE_AUDIO:
2539 status = audio_channel_update(call_med, tmp_pool,
2540 local_sdp, remote_sdp);
2541 if (call->audio_idx==-1 && status==PJ_SUCCESS &&
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002542 call_med->strm.a.stream)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002543 {
2544 call->audio_idx = mi;
2545 }
2546 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00002547#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002548 case PJMEDIA_TYPE_VIDEO:
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002549 status = video_channel_update(call_med, tmp_pool,
2550 local_sdp, remote_sdp);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002551 break;
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002552#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002553 default:
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002554 status = PJMEDIA_EINVALIMEDIATYPE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002555 break;
2556 }
2557
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002558 /* Close the transport of deactivated media, need this here as media
2559 * can be deactivated by the SDP negotiation and the max media count
2560 * (account) setting.
2561 */
2562 if (local_sdp->media[mi]->desc.port==0 && call_med->tp) {
2563 pjmedia_transport_close(call_med->tp);
2564 call_med->tp = call_med->tp_orig = NULL;
Sauw Ming73ecfe82011-09-21 10:20:01 +00002565 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002566 }
2567
Benny Prijono0bc99a92011-03-17 04:34:43 +00002568 if (status != PJ_SUCCESS) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002569 PJ_PERROR(1,(THIS_FILE, status, "Error updating media call%02d:%d",
Benny Prijono0bc99a92011-03-17 04:34:43 +00002570 call_id, mi));
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002571 } else {
2572 got_media = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002573 }
2574 }
2575
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002576 /* Perform SDP re-negotiation if needed. */
2577 if (got_media && need_renego_sdp) {
2578 pjmedia_sdp_neg *neg = call->inv->neg;
2579
2580 /* This should only happen when we are the answerer. */
2581 PJ_ASSERT_RETURN(neg && !pjmedia_sdp_neg_was_answer_remote(neg),
2582 PJMEDIA_SDPNEG_EINSTATE);
2583
2584 status = pjmedia_sdp_neg_set_remote_offer(tmp_pool, neg, remote_sdp);
2585 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002586 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002587
2588 status = pjmedia_sdp_neg_set_local_answer(tmp_pool, neg, local_sdp);
2589 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002590 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002591
2592 status = pjmedia_sdp_neg_negotiate(tmp_pool, neg, 0);
2593 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002594 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002595 }
2596
Benny Prijonob90fd382011-09-18 14:59:56 +00002597 pj_log_pop_indent();
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002598 return (got_media? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA);
Benny Prijonob90fd382011-09-18 14:59:56 +00002599
2600on_error:
2601 pj_log_pop_indent();
2602 return status;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002603}
2604
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002605/*
2606 * Get maxinum number of conference ports.
2607 */
2608PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
2609{
2610 return pjsua_var.media_cfg.max_media_ports;
2611}
2612
2613
2614/*
2615 * Get current number of active ports in the bridge.
2616 */
2617PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
2618{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002619 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002620 unsigned count = PJ_ARRAY_SIZE(ports);
2621 pj_status_t status;
2622
2623 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
2624 if (status != PJ_SUCCESS)
2625 count = 0;
2626
2627 return count;
2628}
2629
2630
2631/*
2632 * Enumerate all conference ports.
2633 */
2634PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
2635 unsigned *count)
2636{
2637 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
2638}
2639
2640
2641/*
2642 * Get information about the specified conference port
2643 */
2644PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
2645 pjsua_conf_port_info *info)
2646{
2647 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00002648 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002649 pj_status_t status;
2650
2651 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
2652 if (status != PJ_SUCCESS)
2653 return status;
2654
Benny Prijonoac623b32006-07-03 15:19:31 +00002655 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002656 info->slot_id = id;
2657 info->name = cinfo.name;
2658 info->clock_rate = cinfo.clock_rate;
2659 info->channel_count = cinfo.channel_count;
2660 info->samples_per_frame = cinfo.samples_per_frame;
2661 info->bits_per_sample = cinfo.bits_per_sample;
2662
2663 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00002664 info->listener_cnt = cinfo.listener_cnt;
2665 for (i=0; i<cinfo.listener_cnt; ++i) {
2666 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002667 }
2668
2669 return PJ_SUCCESS;
2670}
2671
2672
2673/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00002674 * Add arbitrary media port to PJSUA's conference bridge.
2675 */
2676PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
2677 pjmedia_port *port,
2678 pjsua_conf_port_id *p_id)
2679{
2680 pj_status_t status;
2681
2682 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
2683 port, NULL, (unsigned*)p_id);
2684 if (status != PJ_SUCCESS) {
2685 if (p_id)
2686 *p_id = PJSUA_INVALID_ID;
2687 }
2688
2689 return status;
2690}
2691
2692
2693/*
2694 * Remove arbitrary slot from the conference bridge.
2695 */
2696PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
2697{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002698 pj_status_t status;
2699
2700 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
2701 check_snd_dev_idle();
2702
2703 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00002704}
2705
2706
2707/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002708 * Establish unidirectional media flow from souce to sink.
2709 */
2710PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
2711 pjsua_conf_port_id sink)
2712{
Benny Prijonob90fd382011-09-18 14:59:56 +00002713 pj_status_t status = PJ_SUCCESS;
2714
2715 PJ_LOG(4,(THIS_FILE, "%s connect: %d --> %d",
2716 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
2717 source, sink));
2718 pj_log_push_indent();
2719
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002720 /* If sound device idle timer is active, cancel it first. */
Benny Prijono0f711b42009-05-06 19:08:43 +00002721 PJSUA_LOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002722 if (pjsua_var.snd_idle_timer.id) {
2723 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
2724 pjsua_var.snd_idle_timer.id = PJ_FALSE;
2725 }
Benny Prijono0f711b42009-05-06 19:08:43 +00002726 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002727
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002728
Benny Prijonof798e502009-03-09 13:08:16 +00002729 /* For audio switchboard (i.e. APS-Direct):
2730 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002731 * (format, clock rate, channel count) must match to peer's.
2732 * Note that sound device can be reopened only if it doesn't have
2733 * any connection.
2734 */
Benny Prijonof798e502009-03-09 13:08:16 +00002735 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002736 pjmedia_conf_port_info port0_info;
2737 pjmedia_conf_port_info peer_info;
2738 unsigned peer_id;
2739 pj_bool_t need_reopen = PJ_FALSE;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002740
2741 peer_id = (source!=0)? source : sink;
2742 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
2743 &peer_info);
2744 pj_assert(status == PJ_SUCCESS);
2745
2746 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
2747 pj_assert(status == PJ_SUCCESS);
2748
2749 /* Check if sound device is instantiated. */
2750 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2751 !pjsua_var.no_snd);
2752
2753 /* Check if sound device need to reopen because it needs to modify
2754 * settings to match its peer. Sound device must be idle in this case
2755 * though.
2756 */
2757 if (!need_reopen &&
2758 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
2759 {
2760 need_reopen = (peer_info.format.id != port0_info.format.id ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002761 peer_info.format.det.aud.avg_bps !=
2762 port0_info.format.det.aud.avg_bps ||
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002763 peer_info.clock_rate != port0_info.clock_rate ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002764 peer_info.channel_count!=port0_info.channel_count);
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002765 }
2766
2767 if (need_reopen) {
Benny Prijonod65f78c2009-06-03 18:59:37 +00002768 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
Sauw Ming98766c72011-03-11 06:57:24 +00002769 pjmedia_snd_port_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002770
Benny Prijonod65f78c2009-06-03 18:59:37 +00002771 /* Create parameter based on peer info */
Sauw Ming98766c72011-03-11 06:57:24 +00002772 status = create_aud_param(&param.base, pjsua_var.cap_dev,
Benny Prijonod65f78c2009-06-03 18:59:37 +00002773 pjsua_var.play_dev,
2774 peer_info.clock_rate,
2775 peer_info.channel_count,
2776 peer_info.samples_per_frame,
2777 peer_info.bits_per_sample);
2778 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002779 pjsua_perror(THIS_FILE, "Error opening sound device",
2780 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002781 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002782 }
Benny Prijonof798e502009-03-09 13:08:16 +00002783
Benny Prijonod65f78c2009-06-03 18:59:37 +00002784 /* And peer format */
2785 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
Sauw Ming98766c72011-03-11 06:57:24 +00002786 param.base.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
2787 param.base.ext_fmt = peer_info.format;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002788 }
Benny Prijono10454dc2009-02-21 14:21:59 +00002789
Sauw Ming98766c72011-03-11 06:57:24 +00002790 param.options = 0;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002791 status = open_snd_dev(&param);
2792 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002793 pjsua_perror(THIS_FILE, "Error opening sound device",
2794 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002795 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002796 }
2797 } else {
2798 /* Null-audio */
Benny Prijonoc45d9512010-12-10 11:04:30 +00002799 status = pjsua_set_snd_dev(pjsua_var.cap_dev,
2800 pjsua_var.play_dev);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002801 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002802 pjsua_perror(THIS_FILE, "Error opening sound device",
2803 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002804 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002805 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002806 }
Benny Prijono2d647722011-07-13 03:05:22 +00002807 } else if (pjsua_var.no_snd) {
2808 if (!pjsua_var.snd_is_on) {
2809 pjsua_var.snd_is_on = PJ_TRUE;
2810 /* Notify app */
2811 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2812 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2813 }
2814 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002815 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002816
Benny Prijonof798e502009-03-09 13:08:16 +00002817 } else {
2818 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002819
Benny Prijonof798e502009-03-09 13:08:16 +00002820 /* Create sound port if none is instantiated */
2821 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2822 !pjsua_var.no_snd)
2823 {
2824 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002825
Benny Prijonof798e502009-03-09 13:08:16 +00002826 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
2827 if (status != PJ_SUCCESS) {
2828 pjsua_perror(THIS_FILE, "Error opening sound device", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002829 goto on_return;
Benny Prijonof798e502009-03-09 13:08:16 +00002830 }
Benny Prijono2d647722011-07-13 03:05:22 +00002831 } else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) {
2832 pjsua_var.snd_is_on = PJ_TRUE;
2833 /* Notify app */
2834 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2835 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2836 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002837 }
Benny Prijonof798e502009-03-09 13:08:16 +00002838 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002839
Benny Prijonob90fd382011-09-18 14:59:56 +00002840 status = pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
2841
2842on_return:
2843 pj_log_pop_indent();
2844 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002845}
2846
2847
2848/*
2849 * Disconnect media flow from the source to destination port.
2850 */
2851PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
2852 pjsua_conf_port_id sink)
2853{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002854 pj_status_t status;
2855
Benny Prijonob90fd382011-09-18 14:59:56 +00002856 PJ_LOG(4,(THIS_FILE, "%s disconnect: %d -x- %d",
2857 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
2858 source, sink));
2859 pj_log_push_indent();
2860
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002861 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002862 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002863
Benny Prijonob90fd382011-09-18 14:59:56 +00002864 pj_log_pop_indent();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002865 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002866}
2867
2868
Benny Prijono6dd967c2006-12-26 02:27:14 +00002869/*
2870 * Adjust the signal level to be transmitted from the bridge to the
2871 * specified port by making it louder or quieter.
2872 */
2873PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
2874 float level)
2875{
2876 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
2877 (int)((level-1) * 128));
2878}
2879
2880/*
2881 * Adjust the signal level to be received from the specified port (to
2882 * the bridge) by making it louder or quieter.
2883 */
2884PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
2885 float level)
2886{
2887 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
2888 (int)((level-1) * 128));
2889}
2890
2891
2892/*
2893 * Get last signal level transmitted to or received from the specified port.
2894 */
2895PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
2896 unsigned *tx_level,
2897 unsigned *rx_level)
2898{
2899 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
2900 tx_level, rx_level);
2901}
2902
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002903/*****************************************************************************
2904 * File player.
2905 */
2906
Benny Prijonod5696da2007-07-17 16:25:45 +00002907static char* get_basename(const char *path, unsigned len)
2908{
2909 char *p = ((char*)path) + len;
2910
2911 if (len==0)
2912 return p;
2913
Benny Prijono1f61a8f2007-08-16 10:11:44 +00002914 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00002915
2916 return (p==path) ? p : p+1;
2917}
2918
2919
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002920/*
2921 * Create a file player, and automatically connect this player to
2922 * the conference bridge.
2923 */
2924PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
2925 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002926 pjsua_player_id *p_id)
2927{
2928 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002929 char path[PJ_MAXPATH];
Benny Prijonob90fd382011-09-18 14:59:56 +00002930 pj_pool_t *pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002931 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00002932 pj_status_t status = PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002933
2934 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2935 return PJ_ETOOMANY;
2936
Benny Prijonob90fd382011-09-18 14:59:56 +00002937 PJ_LOG(4,(THIS_FILE, "Creating file player: %.*s..",
2938 (int)filename->slen, filename->ptr));
2939 pj_log_push_indent();
2940
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002941 PJSUA_LOCK();
2942
2943 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2944 if (pjsua_var.player[file_id].port == NULL)
2945 break;
2946 }
2947
2948 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2949 /* This is unexpected */
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002950 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00002951 status = PJ_EBUG;
2952 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002953 }
2954
2955 pj_memcpy(path, filename->ptr, filename->slen);
2956 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00002957
2958 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2959 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002960 status = PJ_ENOMEM;
2961 goto on_error;
Benny Prijonod5696da2007-07-17 16:25:45 +00002962 }
2963
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00002964 status = pjmedia_wav_player_port_create(
2965 pool, path,
2966 pjsua_var.mconf_cfg.samples_per_frame *
Benny Prijonoc45d9512010-12-10 11:04:30 +00002967 1000 / pjsua_var.media_cfg.channel_count /
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00002968 pjsua_var.media_cfg.clock_rate,
2969 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002970 if (status != PJ_SUCCESS) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002971 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002972 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002973 }
2974
Benny Prijono5297af92008-03-18 13:40:40 +00002975 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002976 port, filename, &slot);
2977 if (status != PJ_SUCCESS) {
2978 pjmedia_port_destroy(port);
Benny Prijono32e4f492007-01-24 00:44:26 +00002979 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
2980 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002981 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002982 }
2983
Benny Prijonoa66c3312007-01-21 23:12:40 +00002984 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00002985 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002986 pjsua_var.player[file_id].port = port;
2987 pjsua_var.player[file_id].slot = slot;
2988
2989 if (p_id) *p_id = file_id;
2990
2991 ++pjsua_var.player_cnt;
2992
2993 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00002994
2995 PJ_LOG(4,(THIS_FILE, "Player created, id=%d, slot=%d", file_id, slot));
2996
2997 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002998 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00002999
3000on_error:
3001 PJSUA_UNLOCK();
3002 if (pool) pj_pool_release(pool);
3003 pj_log_pop_indent();
3004 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003005}
3006
3007
3008/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00003009 * Create a file playlist media port, and automatically add the port
3010 * to the conference bridge.
3011 */
3012PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
3013 unsigned file_count,
3014 const pj_str_t *label,
3015 unsigned options,
3016 pjsua_player_id *p_id)
3017{
3018 unsigned slot, file_id, ptime;
Benny Prijonob90fd382011-09-18 14:59:56 +00003019 pj_pool_t *pool = NULL;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003020 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00003021 pj_status_t status = PJ_SUCCESS;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003022
3023 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
3024 return PJ_ETOOMANY;
3025
Benny Prijonob90fd382011-09-18 14:59:56 +00003026 PJ_LOG(4,(THIS_FILE, "Creating playlist with %d file(s)..", file_count));
3027 pj_log_push_indent();
3028
Benny Prijonoa66c3312007-01-21 23:12:40 +00003029 PJSUA_LOCK();
3030
3031 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
3032 if (pjsua_var.player[file_id].port == NULL)
3033 break;
3034 }
3035
3036 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
3037 /* This is unexpected */
Benny Prijonoa66c3312007-01-21 23:12:40 +00003038 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00003039 status = PJ_EBUG;
3040 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003041 }
3042
3043
3044 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
3045 pjsua_var.media_cfg.clock_rate;
3046
Benny Prijonod5696da2007-07-17 16:25:45 +00003047 pool = pjsua_pool_create("playlist", 1000, 1000);
3048 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003049 status = PJ_ENOMEM;
3050 goto on_error;
Benny Prijonod5696da2007-07-17 16:25:45 +00003051 }
3052
3053 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00003054 file_names, file_count,
3055 ptime, options, 0, &port);
3056 if (status != PJ_SUCCESS) {
Benny Prijonoa66c3312007-01-21 23:12:40 +00003057 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003058 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003059 }
3060
Benny Prijonod5696da2007-07-17 16:25:45 +00003061 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00003062 port, &port->info.name, &slot);
3063 if (status != PJ_SUCCESS) {
3064 pjmedia_port_destroy(port);
Benny Prijonoa66c3312007-01-21 23:12:40 +00003065 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003066 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003067 }
3068
3069 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00003070 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003071 pjsua_var.player[file_id].port = port;
3072 pjsua_var.player[file_id].slot = slot;
3073
3074 if (p_id) *p_id = file_id;
3075
3076 ++pjsua_var.player_cnt;
3077
3078 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003079
3080 PJ_LOG(4,(THIS_FILE, "Playlist created, id=%d, slot=%d", file_id, slot));
3081
3082 pj_log_pop_indent();
3083
Benny Prijonoa66c3312007-01-21 23:12:40 +00003084 return PJ_SUCCESS;
3085
Benny Prijonob90fd382011-09-18 14:59:56 +00003086on_error:
3087 PJSUA_UNLOCK();
3088 if (pool) pj_pool_release(pool);
3089 pj_log_pop_indent();
3090
3091 return status;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003092}
3093
3094
3095/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003096 * Get conference port ID associated with player.
3097 */
3098PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
3099{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003100 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003101 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
3102
3103 return pjsua_var.player[id].slot;
3104}
3105
Benny Prijono469b1522006-12-26 03:05:17 +00003106/*
3107 * Get the media port for the player.
3108 */
Benny Prijonobe41d862008-01-18 13:24:28 +00003109PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00003110 pjmedia_port **p_port)
3111{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003112 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00003113 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
3114 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
3115
3116 *p_port = pjsua_var.player[id].port;
3117
3118 return PJ_SUCCESS;
3119}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003120
3121/*
3122 * Set playback position.
3123 */
3124PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
3125 pj_uint32_t samples)
3126{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003127 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003128 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00003129 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003130
3131 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
3132}
3133
3134
3135/*
3136 * Close the file, remove the player from the bridge, and free
3137 * resources associated with the file player.
3138 */
3139PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
3140{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003141 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003142 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
3143
Benny Prijonob90fd382011-09-18 14:59:56 +00003144 PJ_LOG(4,(THIS_FILE, "Destroying player %d..", id));
3145 pj_log_push_indent();
3146
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003147 PJSUA_LOCK();
3148
3149 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00003150 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003151 pjmedia_port_destroy(pjsua_var.player[id].port);
3152 pjsua_var.player[id].port = NULL;
3153 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00003154 pj_pool_release(pjsua_var.player[id].pool);
3155 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003156 pjsua_var.player_cnt--;
3157 }
3158
3159 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003160 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003161
3162 return PJ_SUCCESS;
3163}
3164
3165
3166/*****************************************************************************
3167 * File recorder.
3168 */
3169
3170/*
3171 * Create a file recorder, and automatically connect this recorder to
3172 * the conference bridge.
3173 */
3174PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00003175 unsigned enc_type,
3176 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003177 pj_ssize_t max_size,
3178 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003179 pjsua_recorder_id *p_id)
3180{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003181 enum Format
3182 {
3183 FMT_UNKNOWN,
3184 FMT_WAV,
3185 FMT_MP3,
3186 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003187 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003188 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003189 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00003190 int file_format;
Benny Prijonob90fd382011-09-18 14:59:56 +00003191 pj_pool_t *pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003192 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00003193 pj_status_t status = PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003194
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003195 /* Filename must present */
3196 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
3197
Benny Prijono00cae612006-07-31 15:19:36 +00003198 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003199 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00003200
Benny Prijono8f310522006-10-20 11:08:49 +00003201 /* Don't support encoding type at present */
3202 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00003203
Benny Prijonob90fd382011-09-18 14:59:56 +00003204 PJ_LOG(4,(THIS_FILE, "Creating recorder %.*s..",
3205 (int)filename->slen, filename->ptr));
3206 pj_log_push_indent();
3207
3208 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) {
3209 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003210 return PJ_ETOOMANY;
Benny Prijonob90fd382011-09-18 14:59:56 +00003211 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003212
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003213 /* Determine the file format */
3214 ext.ptr = filename->ptr + filename->slen - 4;
3215 ext.slen = 4;
3216
3217 if (pj_stricmp2(&ext, ".wav") == 0)
3218 file_format = FMT_WAV;
3219 else if (pj_stricmp2(&ext, ".mp3") == 0)
3220 file_format = FMT_MP3;
3221 else {
3222 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
3223 "determine file format for %.*s",
3224 (int)filename->slen, filename->ptr));
Benny Prijonob90fd382011-09-18 14:59:56 +00003225 pj_log_pop_indent();
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003226 return PJ_ENOTSUP;
3227 }
3228
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003229 PJSUA_LOCK();
3230
3231 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
3232 if (pjsua_var.recorder[file_id].port == NULL)
3233 break;
3234 }
3235
3236 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
3237 /* This is unexpected */
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003238 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00003239 status = PJ_EBUG;
3240 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003241 }
3242
3243 pj_memcpy(path, filename->ptr, filename->slen);
3244 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003245
Benny Prijonod5696da2007-07-17 16:25:45 +00003246 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
3247 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003248 status = PJ_ENOMEM;
3249 goto on_return;
Benny Prijonod5696da2007-07-17 16:25:45 +00003250 }
3251
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003252 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00003253 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003254 pjsua_var.media_cfg.clock_rate,
3255 pjsua_var.mconf_cfg.channel_count,
3256 pjsua_var.mconf_cfg.samples_per_frame,
3257 pjsua_var.mconf_cfg.bits_per_sample,
3258 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003259 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00003260 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003261 port = NULL;
3262 status = PJ_ENOTSUP;
3263 }
3264
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003265 if (status != PJ_SUCCESS) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003266 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003267 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003268 }
3269
Benny Prijonod5696da2007-07-17 16:25:45 +00003270 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003271 port, filename, &slot);
3272 if (status != PJ_SUCCESS) {
3273 pjmedia_port_destroy(port);
Benny Prijonob90fd382011-09-18 14:59:56 +00003274 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003275 }
3276
3277 pjsua_var.recorder[file_id].port = port;
3278 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00003279 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003280
3281 if (p_id) *p_id = file_id;
3282
3283 ++pjsua_var.rec_cnt;
3284
3285 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003286
3287 PJ_LOG(4,(THIS_FILE, "Recorder created, id=%d, slot=%d", file_id, slot));
3288
3289 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003290 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003291
3292on_return:
3293 PJSUA_UNLOCK();
3294 if (pool) pj_pool_release(pool);
3295 pj_log_pop_indent();
3296 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003297}
3298
3299
3300/*
3301 * Get conference port associated with recorder.
3302 */
3303PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
3304{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003305 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3306 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003307 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3308
3309 return pjsua_var.recorder[id].slot;
3310}
3311
Benny Prijono469b1522006-12-26 03:05:17 +00003312/*
3313 * Get the media port for the recorder.
3314 */
3315PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
3316 pjmedia_port **p_port)
3317{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003318 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3319 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00003320 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3321 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
3322
3323 *p_port = pjsua_var.recorder[id].port;
3324 return PJ_SUCCESS;
3325}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003326
3327/*
3328 * Destroy recorder (this will complete recording).
3329 */
3330PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
3331{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003332 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3333 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003334 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3335
Benny Prijonob90fd382011-09-18 14:59:56 +00003336 PJ_LOG(4,(THIS_FILE, "Destroying recorder %d..", id));
3337 pj_log_push_indent();
3338
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003339 PJSUA_LOCK();
3340
3341 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00003342 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003343 pjmedia_port_destroy(pjsua_var.recorder[id].port);
3344 pjsua_var.recorder[id].port = NULL;
3345 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00003346 pj_pool_release(pjsua_var.recorder[id].pool);
3347 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003348 pjsua_var.rec_cnt--;
3349 }
3350
3351 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003352 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003353
3354 return PJ_SUCCESS;
3355}
3356
3357
3358/*****************************************************************************
3359 * Sound devices.
3360 */
3361
3362/*
3363 * Enum sound devices.
3364 */
Benny Prijonof798e502009-03-09 13:08:16 +00003365
3366PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003367 unsigned *count)
3368{
3369 unsigned i, dev_count;
3370
Benny Prijono10454dc2009-02-21 14:21:59 +00003371 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003372
3373 if (dev_count > *count) dev_count = *count;
3374
3375 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00003376 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003377
Benny Prijono10454dc2009-02-21 14:21:59 +00003378 status = pjmedia_aud_dev_get_info(i, &info[i]);
3379 if (status != PJ_SUCCESS)
3380 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003381 }
3382
3383 *count = dev_count;
3384
3385 return PJ_SUCCESS;
3386}
Benny Prijonof798e502009-03-09 13:08:16 +00003387
3388
Benny Prijono10454dc2009-02-21 14:21:59 +00003389PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
3390 unsigned *count)
3391{
3392 unsigned i, dev_count;
3393
3394 dev_count = pjmedia_aud_dev_count();
3395
3396 if (dev_count > *count) dev_count = *count;
3397 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
3398
3399 for (i=0; i<dev_count; ++i) {
3400 pjmedia_aud_dev_info ai;
3401 pj_status_t status;
3402
3403 status = pjmedia_aud_dev_get_info(i, &ai);
3404 if (status != PJ_SUCCESS)
3405 return status;
3406
3407 strncpy(info[i].name, ai.name, sizeof(info[i].name));
3408 info[i].name[sizeof(info[i].name)-1] = '\0';
3409 info[i].input_count = ai.input_count;
3410 info[i].output_count = ai.output_count;
3411 info[i].default_samples_per_sec = ai.default_samples_per_sec;
3412 }
3413
3414 *count = dev_count;
3415
3416 return PJ_SUCCESS;
3417}
Benny Prijono10454dc2009-02-21 14:21:59 +00003418
Benny Prijonof798e502009-03-09 13:08:16 +00003419/* Create audio device parameter to open the device */
3420static pj_status_t create_aud_param(pjmedia_aud_param *param,
3421 pjmedia_aud_dev_index capture_dev,
3422 pjmedia_aud_dev_index playback_dev,
3423 unsigned clock_rate,
3424 unsigned channel_count,
3425 unsigned samples_per_frame,
3426 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003427{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003428 pj_status_t status;
3429
Benny Prijono96e74f32009-02-22 12:00:12 +00003430 /* Normalize device ID with new convention about default device ID */
3431 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
3432 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
3433
Benny Prijono10454dc2009-02-21 14:21:59 +00003434 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00003435 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00003436 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00003437 pjsua_perror(THIS_FILE, "Error retrieving default audio "
3438 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00003439 return status;
3440 }
Benny Prijonof798e502009-03-09 13:08:16 +00003441 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
3442 param->rec_id = capture_dev;
3443 param->play_id = playback_dev;
3444 param->clock_rate = clock_rate;
3445 param->channel_count = channel_count;
3446 param->samples_per_frame = samples_per_frame;
3447 param->bits_per_sample = bits_per_sample;
3448
3449 /* Update the setting with user preference */
3450#define update_param(cap, field) \
3451 if (pjsua_var.aud_param.flags & cap) { \
3452 param->flags |= cap; \
3453 param->field = pjsua_var.aud_param.field; \
3454 }
3455 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3456 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3457 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3458 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3459#undef update_param
3460
Benny Prijono10454dc2009-02-21 14:21:59 +00003461 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00003462 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
3463 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
3464 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
3465 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
3466
Benny Prijono10454dc2009-02-21 14:21:59 +00003467 /* EC settings */
3468 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00003469 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
3470 param->ec_enabled = PJ_TRUE;
3471 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00003472 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00003473 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00003474 }
3475
Benny Prijonof798e502009-03-09 13:08:16 +00003476 return PJ_SUCCESS;
3477}
Benny Prijono26056d82006-10-11 16:03:41 +00003478
Benny Prijonof798e502009-03-09 13:08:16 +00003479/* Internal: the first time the audio device is opened (during app
3480 * startup), retrieve the audio settings such as volume level
3481 * so that aud_get_settings() will work.
3482 */
3483static pj_status_t update_initial_aud_param()
3484{
3485 pjmedia_aud_stream *strm;
3486 pjmedia_aud_param param;
3487 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00003488
Benny Prijonof798e502009-03-09 13:08:16 +00003489 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00003490
Benny Prijonof798e502009-03-09 13:08:16 +00003491 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00003492
Benny Prijonof798e502009-03-09 13:08:16 +00003493 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003494 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00003495 pjsua_perror(THIS_FILE, "Error audio stream "
3496 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003497 return status;
3498 }
3499
Benny Prijonof798e502009-03-09 13:08:16 +00003500#define update_saved_param(cap, field) \
3501 if (param.flags & cap) { \
3502 pjsua_var.aud_param.flags |= cap; \
3503 pjsua_var.aud_param.field = param.field; \
3504 }
3505
3506 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3507 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3508 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3509 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3510#undef update_saved_param
3511
3512 return PJ_SUCCESS;
3513}
3514
3515/* Get format name */
3516static const char *get_fmt_name(pj_uint32_t id)
3517{
3518 static char name[8];
3519
3520 if (id == PJMEDIA_FORMAT_L16)
3521 return "PCM";
3522 pj_memcpy(name, &id, 4);
3523 name[4] = '\0';
3524 return name;
3525}
3526
3527/* Open sound device with the setting. */
Sauw Ming98766c72011-03-11 06:57:24 +00003528static pj_status_t open_snd_dev(pjmedia_snd_port_param *param)
Benny Prijonof798e502009-03-09 13:08:16 +00003529{
3530 pjmedia_port *conf_port;
3531 pj_status_t status;
3532
3533 PJ_ASSERT_RETURN(param, PJ_EINVAL);
3534
3535 /* Check if NULL sound device is used */
Sauw Ming98766c72011-03-11 06:57:24 +00003536 if (NULL_SND_DEV_ID==param->base.rec_id ||
3537 NULL_SND_DEV_ID==param->base.play_id)
3538 {
Benny Prijonof798e502009-03-09 13:08:16 +00003539 return pjsua_set_null_snd_dev();
3540 }
3541
3542 /* Close existing sound port */
3543 close_snd_dev();
3544
Benny Prijono2d647722011-07-13 03:05:22 +00003545 /* Notify app */
3546 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3547 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
3548 }
3549
Benny Prijonof798e502009-03-09 13:08:16 +00003550 /* Create memory pool for sound device. */
3551 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3552 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3553
3554
3555 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
Sauw Ming98766c72011-03-11 06:57:24 +00003556 get_fmt_name(param->base.ext_fmt.id),
3557 param->base.clock_rate, param->base.channel_count,
3558 param->base.samples_per_frame / param->base.channel_count *
3559 1000 / param->base.clock_rate));
Benny Prijonob90fd382011-09-18 14:59:56 +00003560 pj_log_push_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003561
3562 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
Sauw Ming98766c72011-03-11 06:57:24 +00003563 param, &pjsua_var.snd_port);
Benny Prijonof798e502009-03-09 13:08:16 +00003564 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00003565 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003566
3567 /* Get the port0 of the conference bridge. */
3568 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3569 pj_assert(conf_port != NULL);
3570
3571 /* For conference bridge, resample if necessary if the bridge's
3572 * clock rate is different than the sound device's clock rate.
3573 */
3574 if (!pjsua_var.is_mswitch &&
Sauw Ming98766c72011-03-11 06:57:24 +00003575 param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM &&
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003576 PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate)
Benny Prijonof798e502009-03-09 13:08:16 +00003577 {
3578 pjmedia_port *resample_port;
3579 unsigned resample_opt = 0;
3580
3581 if (pjsua_var.media_cfg.quality >= 3 &&
3582 pjsua_var.media_cfg.quality <= 4)
3583 {
3584 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
3585 }
3586 else if (pjsua_var.media_cfg.quality < 3) {
3587 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
3588 }
3589
3590 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
3591 conf_port,
Sauw Ming98766c72011-03-11 06:57:24 +00003592 param->base.clock_rate,
Benny Prijonof798e502009-03-09 13:08:16 +00003593 resample_opt,
3594 &resample_port);
3595 if (status != PJ_SUCCESS) {
3596 char errmsg[PJ_ERR_MSG_SIZE];
3597 pj_strerror(status, errmsg, sizeof(errmsg));
3598 PJ_LOG(4, (THIS_FILE,
3599 "Error creating resample port: %s",
3600 errmsg));
3601 close_snd_dev();
Benny Prijonob90fd382011-09-18 14:59:56 +00003602 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003603 }
3604
3605 conf_port = resample_port;
3606 }
3607
3608 /* Otherwise for audio switchboard, the switch's port0 setting is
3609 * derived from the sound device setting, so update the setting.
3610 */
3611 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003612 pj_memcpy(&conf_port->info.fmt, &param->base.ext_fmt,
Benny Prijonoc45d9512010-12-10 11:04:30 +00003613 sizeof(conf_port->info.fmt));
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003614 conf_port->info.fmt.det.aud.clock_rate = param->base.clock_rate;
3615 conf_port->info.fmt.det.aud.frame_time_usec = param->base.samples_per_frame*
Benny Prijonoc45d9512010-12-10 11:04:30 +00003616 1000000 /
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003617 param->base.clock_rate;
3618 conf_port->info.fmt.det.aud.channel_count = param->base.channel_count;
Benny Prijonoc45d9512010-12-10 11:04:30 +00003619 conf_port->info.fmt.det.aud.bits_per_sample = 16;
Benny Prijonof798e502009-03-09 13:08:16 +00003620 }
3621
Benny Prijonoc45d9512010-12-10 11:04:30 +00003622
Benny Prijonof798e502009-03-09 13:08:16 +00003623 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00003624 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
3625 conf_port );
3626 if (status != PJ_SUCCESS) {
3627 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
3628 "sound device", status);
3629 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3630 pjsua_var.snd_port = NULL;
Benny Prijonob90fd382011-09-18 14:59:56 +00003631 goto on_error;
Benny Prijono52a93912006-08-04 20:54:37 +00003632 }
3633
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003634 /* Save the device IDs */
Sauw Ming98766c72011-03-11 06:57:24 +00003635 pjsua_var.cap_dev = param->base.rec_id;
3636 pjsua_var.play_dev = param->base.play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003637
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003638 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00003639 {
3640 pjmedia_aud_dev_info rec_info;
3641 pjmedia_aud_stream *strm;
3642 pjmedia_aud_param si;
3643 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003644
Benny Prijonof798e502009-03-09 13:08:16 +00003645 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3646 status = pjmedia_aud_stream_get_param(strm, &si);
3647 if (status == PJ_SUCCESS)
3648 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00003649
Benny Prijonof798e502009-03-09 13:08:16 +00003650 if (status==PJ_SUCCESS) {
Sauw Ming98766c72011-03-11 06:57:24 +00003651 if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) {
Benny Prijonof798e502009-03-09 13:08:16 +00003652 char tmp_buf[128];
3653 int tmp_buf_len = sizeof(tmp_buf);
3654
3655 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
3656 "%s (%dKHz)",
3657 rec_info.name,
Sauw Ming98766c72011-03-11 06:57:24 +00003658 param->base.clock_rate/1000);
Benny Prijonof798e502009-03-09 13:08:16 +00003659 pj_strset(&tmp, tmp_buf, tmp_buf_len);
3660 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
3661 } else {
3662 pjmedia_conf_set_port0_name(pjsua_var.mconf,
3663 pj_cstr(&tmp, rec_info.name));
3664 }
3665 }
3666
3667 /* Any error is not major, let it through */
3668 status = PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003669 }
Benny Prijonof798e502009-03-09 13:08:16 +00003670
3671 /* If this is the first time the audio device is open, retrieve some
3672 * settings from the device (such as volume settings) so that the
3673 * pjsua_snd_get_setting() work.
3674 */
3675 if (pjsua_var.aud_open_cnt == 0) {
3676 update_initial_aud_param();
3677 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00003678 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003679
Benny Prijonob90fd382011-09-18 14:59:56 +00003680 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003681 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003682
3683on_error:
3684 pj_log_pop_indent();
3685 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00003686}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003687
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003688
Benny Prijonof798e502009-03-09 13:08:16 +00003689/* Close existing sound device */
3690static void close_snd_dev(void)
3691{
Benny Prijonob90fd382011-09-18 14:59:56 +00003692 pj_log_push_indent();
3693
Benny Prijono2d647722011-07-13 03:05:22 +00003694 /* Notify app */
3695 if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3696 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0);
3697 }
3698
Benny Prijonof798e502009-03-09 13:08:16 +00003699 /* Close sound device */
3700 if (pjsua_var.snd_port) {
3701 pjmedia_aud_dev_info cap_info, play_info;
3702 pjmedia_aud_stream *strm;
3703 pjmedia_aud_param param;
3704
3705 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3706 pjmedia_aud_stream_get_param(strm, &param);
3707
3708 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
3709 cap_info.name[0] = '\0';
3710 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
3711 play_info.name[0] = '\0';
3712
3713 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
3714 "%s sound capture device",
3715 play_info.name, cap_info.name));
3716
3717 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
3718 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3719 pjsua_var.snd_port = NULL;
3720 }
3721
3722 /* Close null sound device */
3723 if (pjsua_var.null_snd) {
3724 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
3725 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
3726 pjsua_var.null_snd = NULL;
3727 }
3728
3729 if (pjsua_var.snd_pool)
3730 pj_pool_release(pjsua_var.snd_pool);
3731 pjsua_var.snd_pool = NULL;
Benny Prijono2d647722011-07-13 03:05:22 +00003732 pjsua_var.snd_is_on = PJ_FALSE;
Benny Prijonob90fd382011-09-18 14:59:56 +00003733
3734 pj_log_pop_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003735}
3736
3737
3738/*
3739 * Select or change sound device. Application may call this function at
3740 * any time to replace current sound device.
3741 */
3742PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
3743 int playback_dev)
3744{
3745 unsigned alt_cr_cnt = 1;
3746 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
3747 unsigned i;
3748 pj_status_t status = -1;
3749
Benny Prijonob90fd382011-09-18 14:59:56 +00003750 PJ_LOG(4,(THIS_FILE, "Set sound device: capture=%d, playback=%d",
3751 capture_dev, playback_dev));
3752 pj_log_push_indent();
3753
Benny Prijono23ea21a2009-06-03 12:43:06 +00003754 /* Null-sound */
3755 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003756 status = pjsua_set_null_snd_dev();
3757 pj_log_pop_indent();
3758 return status;
Benny Prijono23ea21a2009-06-03 12:43:06 +00003759 }
3760
Benny Prijonof798e502009-03-09 13:08:16 +00003761 /* Set default clock rate */
3762 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
3763 if (alt_cr[0] == 0)
3764 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
3765
3766 /* Allow retrying of different clock rate if we're using conference
3767 * bridge (meaning audio format is always PCM), otherwise lock on
3768 * to one clock rate.
3769 */
3770 if (pjsua_var.is_mswitch) {
3771 alt_cr_cnt = 1;
3772 } else {
3773 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
3774 }
3775
3776 /* Attempts to open the sound device with different clock rates */
3777 for (i=0; i<alt_cr_cnt; ++i) {
Sauw Ming98766c72011-03-11 06:57:24 +00003778 pjmedia_snd_port_param param;
Benny Prijonof798e502009-03-09 13:08:16 +00003779 unsigned samples_per_frame;
3780
3781 /* Create the default audio param */
3782 samples_per_frame = alt_cr[i] *
3783 pjsua_var.media_cfg.audio_frame_ptime *
3784 pjsua_var.media_cfg.channel_count / 1000;
Sauw Ming98766c72011-03-11 06:57:24 +00003785 status = create_aud_param(&param.base, capture_dev, playback_dev,
Benny Prijonof798e502009-03-09 13:08:16 +00003786 alt_cr[i], pjsua_var.media_cfg.channel_count,
3787 samples_per_frame, 16);
3788 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00003789 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003790
3791 /* Open! */
Sauw Ming98766c72011-03-11 06:57:24 +00003792 param.options = 0;
Benny Prijonof798e502009-03-09 13:08:16 +00003793 status = open_snd_dev(&param);
3794 if (status == PJ_SUCCESS)
3795 break;
3796 }
3797
3798 if (status != PJ_SUCCESS) {
3799 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003800 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003801 }
3802
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003803 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00003804 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003805
Benny Prijonob90fd382011-09-18 14:59:56 +00003806 pj_log_pop_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003807 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003808
3809on_error:
3810 pj_log_pop_indent();
3811 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003812}
3813
3814
3815/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00003816 * Get currently active sound devices. If sound devices has not been created
3817 * (for example when pjsua_start() is not called), it is possible that
3818 * the function returns PJ_SUCCESS with -1 as device IDs.
3819 */
3820PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
3821 int *playback_dev)
3822{
3823 if (capture_dev) {
3824 *capture_dev = pjsua_var.cap_dev;
3825 }
3826 if (playback_dev) {
3827 *playback_dev = pjsua_var.play_dev;
3828 }
3829
3830 return PJ_SUCCESS;
3831}
3832
3833
3834/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003835 * Use null sound device.
3836 */
3837PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
3838{
3839 pjmedia_port *conf_port;
3840 pj_status_t status;
3841
Benny Prijonob90fd382011-09-18 14:59:56 +00003842 PJ_LOG(4,(THIS_FILE, "Setting null sound device.."));
3843 pj_log_push_indent();
3844
3845
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003846 /* Close existing sound device */
3847 close_snd_dev();
3848
Benny Prijono2d647722011-07-13 03:05:22 +00003849 /* Notify app */
3850 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3851 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
3852 }
3853
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003854 /* Create memory pool for sound device. */
3855 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3856 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3857
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003858 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
3859
3860 /* Get the port0 of the conference bridge. */
3861 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3862 pj_assert(conf_port != NULL);
3863
3864 /* Create master port, connecting port0 of the conference bridge to
3865 * a null port.
3866 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003867 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003868 conf_port, 0, &pjsua_var.null_snd);
3869 if (status != PJ_SUCCESS) {
3870 pjsua_perror(THIS_FILE, "Unable to create null sound device",
3871 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003872 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003873 return status;
3874 }
3875
3876 /* Start the master port */
3877 status = pjmedia_master_port_start(pjsua_var.null_snd);
3878 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
3879
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003880 pjsua_var.cap_dev = NULL_SND_DEV_ID;
3881 pjsua_var.play_dev = NULL_SND_DEV_ID;
3882
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003883 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00003884 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003885
Benny Prijonob90fd382011-09-18 14:59:56 +00003886 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003887 return PJ_SUCCESS;
3888}
3889
3890
Benny Prijonoe909eac2006-07-27 22:04:56 +00003891
3892/*
3893 * Use no device!
3894 */
3895PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
3896{
3897 /* Close existing sound device */
3898 close_snd_dev();
3899
3900 pjsua_var.no_snd = PJ_TRUE;
3901 return pjmedia_conf_get_master_port(pjsua_var.mconf);
3902}
3903
3904
Benny Prijonof20687a2006-08-04 18:27:19 +00003905/*
3906 * Configure the AEC settings of the sound port.
3907 */
Benny Prijono5da50432006-08-07 10:24:52 +00003908PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00003909{
3910 pjsua_var.media_cfg.ec_tail_len = tail_ms;
3911
3912 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00003913 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
3914 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00003915
3916 return PJ_SUCCESS;
3917}
3918
3919
3920/*
3921 * Get current AEC tail length.
3922 */
Benny Prijono22dfe592006-08-06 12:07:13 +00003923PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00003924{
3925 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
3926 return PJ_SUCCESS;
3927}
3928
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00003929
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003930/*
Benny Prijonof798e502009-03-09 13:08:16 +00003931 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003932 */
Benny Prijonof798e502009-03-09 13:08:16 +00003933PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003934{
Benny Prijonof798e502009-03-09 13:08:16 +00003935 return pjsua_var.snd_port != NULL;
3936}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003937
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003938
Benny Prijonof798e502009-03-09 13:08:16 +00003939/*
3940 * Configure sound device setting to the sound device being used.
3941 */
3942PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
3943 const void *pval,
3944 pj_bool_t keep)
3945{
Benny Prijono09b0ff62009-03-10 12:07:51 +00003946 pj_status_t status;
3947
Benny Prijonof798e502009-03-09 13:08:16 +00003948 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003949 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00003950 return PJMEDIA_EAUD_INVCAP;
3951 }
3952
Benny Prijono09b0ff62009-03-10 12:07:51 +00003953 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00003954 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00003955 pjmedia_aud_stream *strm;
3956
3957 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003958 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00003959 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00003960 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00003961 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00003962
3963 if (status != PJ_SUCCESS)
3964 return status;
3965
3966 /* Save in internal param for later device open */
3967 if (keep) {
3968 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
3969 cap, pval);
3970 }
3971
3972 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00003973}
3974
3975/*
3976 * Retrieve a sound device setting.
3977 */
3978PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
3979 void *pval)
3980{
3981 /* If sound device has never been opened before, open it to
3982 * retrieve the initial setting from the device (e.g. audio
3983 * volume)
3984 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003985 if (pjsua_var.aud_open_cnt==0) {
3986 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00003987 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003988 close_snd_dev();
3989 }
Benny Prijonof798e502009-03-09 13:08:16 +00003990
3991 if (pjsua_snd_is_active()) {
3992 /* Sound is active, retrieve from device directly */
3993 pjmedia_aud_stream *strm;
3994
3995 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3996 return pjmedia_aud_stream_get_cap(strm, cap, pval);
3997 } else {
3998 /* Otherwise retrieve from internal param */
3999 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
4000 cap, pval);
4001 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00004002}
4003
4004
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004005/*****************************************************************************
4006 * Codecs.
4007 */
4008
4009/*
4010 * Enum all supported codecs in the system.
4011 */
4012PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
4013 unsigned *p_count )
4014{
4015 pjmedia_codec_mgr *codec_mgr;
4016 pjmedia_codec_info info[32];
4017 unsigned i, count, prio[32];
4018 pj_status_t status;
4019
4020 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4021 count = PJ_ARRAY_SIZE(info);
4022 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
4023 if (status != PJ_SUCCESS) {
4024 *p_count = 0;
4025 return status;
4026 }
4027
4028 if (count > *p_count) count = *p_count;
4029
4030 for (i=0; i<count; ++i) {
Nanang Izzuddin56b2ce42011-04-06 13:55:01 +00004031 pj_bzero(&id[i], sizeof(pjsua_codec_info));
4032
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004033 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
4034 id[i].codec_id = pj_str(id[i].buf_);
4035 id[i].priority = (pj_uint8_t) prio[i];
4036 }
4037
4038 *p_count = count;
4039
4040 return PJ_SUCCESS;
4041}
4042
4043
4044/*
4045 * Change codec priority.
4046 */
4047PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
4048 pj_uint8_t priority )
4049{
Benny Prijono88accae2008-06-26 15:48:14 +00004050 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004051 pjmedia_codec_mgr *codec_mgr;
4052
4053 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4054
Benny Prijono88accae2008-06-26 15:48:14 +00004055 if (codec_id->slen==1 && *codec_id->ptr=='*')
4056 codec_id = &all;
4057
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004058 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
4059 priority);
4060}
4061
4062
4063/*
4064 * Get codec parameters.
4065 */
4066PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
4067 pjmedia_codec_param *param )
4068{
Benny Prijono88accae2008-06-26 15:48:14 +00004069 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004070 const pjmedia_codec_info *info;
4071 pjmedia_codec_mgr *codec_mgr;
4072 unsigned count = 1;
4073 pj_status_t status;
4074
4075 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4076
Benny Prijono88accae2008-06-26 15:48:14 +00004077 if (codec_id->slen==1 && *codec_id->ptr=='*')
4078 codec_id = &all;
4079
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004080 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
4081 &count, &info, NULL);
4082 if (status != PJ_SUCCESS)
4083 return status;
4084
4085 if (count != 1)
Nanang Izzuddin50fae732011-03-22 09:49:23 +00004086 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004087
4088 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
4089 return status;
4090}
4091
4092
4093/*
4094 * Set codec parameters.
4095 */
Nanang Izzuddin06839e72010-01-27 11:48:31 +00004096PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004097 const pjmedia_codec_param *param)
4098{
Nanang Izzuddin06839e72010-01-27 11:48:31 +00004099 const pjmedia_codec_info *info[2];
4100 pjmedia_codec_mgr *codec_mgr;
4101 unsigned count = 2;
4102 pj_status_t status;
4103
4104 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4105
4106 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
4107 &count, info, NULL);
4108 if (status != PJ_SUCCESS)
4109 return status;
4110
4111 /* Codec ID should be specific, except for G.722.1 */
4112 if (count > 1 &&
4113 pj_strnicmp2(codec_id, "G7221/16", 8) != 0 &&
4114 pj_strnicmp2(codec_id, "G7221/32", 8) != 0)
4115 {
4116 pj_assert(!"Codec ID is not specific");
4117 return PJ_ETOOMANY;
4118 }
4119
4120 status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param);
4121 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004122}
Nanang Izzuddin50fae732011-03-22 09:49:23 +00004123
4124