blob: 917dadcb668501f96f924c98054c4c4b71b442f0 [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 &&
Nanang Izzuddin5be47c72011-12-15 06:27:07 +0000153 ext_fmts[j].det.aud.avg_bps ==
154 aud_info.ext_fmt[i].det.aud.avg_bps)
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000155 {
156 is_listed = PJ_TRUE;
157 }
158 }
159
160 /* Put this format into the list, if it is not in the list */
161 if (!is_listed)
162 ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i];
163
164 pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts));
165 }
166 }
167
168 /* Init the passthrough codec with supported formats only */
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000169 codec_cfg.passthrough.setting.fmt_cnt = ext_fmt_cnt;
170 codec_cfg.passthrough.setting.fmts = ext_fmts;
171 codec_cfg.passthrough.setting.ilbc_mode = cfg->ilbc_mode;
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000172 }
173#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
174
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000175 /* Register all codecs */
176 status = pjmedia_codec_register_audio_codecs(pjsua_var.med_endpt,
177 &codec_cfg);
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000178 if (status != PJ_SUCCESS) {
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000179 PJ_PERROR(1,(THIS_FILE, status, "Error registering codecs"));
Benny Prijonob90fd382011-09-18 14:59:56 +0000180 goto on_error;
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000181 }
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000182
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000183 /* Set speex/16000 to higher priority*/
184 codec_id = pj_str("speex/16000");
185 pjmedia_codec_mgr_set_codec_priority(
186 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
187 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
188
189 /* Set speex/8000 to next higher priority*/
190 codec_id = pj_str("speex/8000");
191 pjmedia_codec_mgr_set_codec_priority(
192 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
193 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000194
195 /* Disable ALL L16 codecs */
196 codec_id = pj_str("L16");
197 pjmedia_codec_mgr_set_codec_priority(
198 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
199 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
200
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000201
202 /* Save additional conference bridge parameters for future
203 * reference.
204 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000205 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000206 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000207 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
208 pjsua_var.mconf_cfg.channel_count *
209 pjsua_var.media_cfg.audio_frame_ptime /
210 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000211
Benny Prijono0498d902006-06-19 14:49:14 +0000212 /* Init options for conference bridge. */
213 opt = PJMEDIA_CONF_NO_DEVICE;
214 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000215 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000216 {
217 opt |= PJMEDIA_CONF_SMALL_FILTER;
218 }
219 else if (pjsua_var.media_cfg.quality < 3) {
220 opt |= PJMEDIA_CONF_USE_LINEAR;
221 }
222
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000223 /* Init conference bridge. */
224 status = pjmedia_conf_create(pjsua_var.pool,
225 pjsua_var.media_cfg.max_media_ports,
226 pjsua_var.media_cfg.clock_rate,
227 pjsua_var.mconf_cfg.channel_count,
228 pjsua_var.mconf_cfg.samples_per_frame,
229 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000230 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000231 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000232 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000233 status);
Benny Prijonob90fd382011-09-18 14:59:56 +0000234 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000235 }
236
Benny Prijonof798e502009-03-09 13:08:16 +0000237 /* Are we using the audio switchboard (a.k.a APS-Direct)? */
238 pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
239 ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
240
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000241 /* Create null port just in case user wants to use null sound. */
242 status = pjmedia_null_port_create(pjsua_var.pool,
243 pjsua_var.media_cfg.clock_rate,
244 pjsua_var.mconf_cfg.channel_count,
245 pjsua_var.mconf_cfg.samples_per_frame,
246 pjsua_var.mconf_cfg.bits_per_sample,
247 &pjsua_var.null_port);
248 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
249
Nanang Izzuddin69b69ae2009-04-14 15:18:30 +0000250#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
251 /* Initialize SRTP library. */
252 status = pjmedia_srtp_init_lib();
253 if (status != PJ_SUCCESS) {
254 pjsua_perror(THIS_FILE, "Error initializing SRTP library",
255 status);
Benny Prijonob90fd382011-09-18 14:59:56 +0000256 goto on_error;
Nanang Izzuddin69b69ae2009-04-14 15:18:30 +0000257 }
258#endif
259
Benny Prijono9f468d12011-07-07 07:46:33 +0000260 /* Video */
261#if PJMEDIA_HAS_VIDEO
262 status = pjsua_vid_subsys_init();
263 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +0000264 goto on_error;
Benny Prijono9f468d12011-07-07 07:46:33 +0000265#endif
266
Benny Prijonob90fd382011-09-18 14:59:56 +0000267 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000268 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +0000269
270on_error:
271 pj_log_pop_indent();
272 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000273}
274
275
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000276/* Check if sound device is idle. */
277static void check_snd_dev_idle()
278{
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000279 unsigned call_cnt;
280
281 /* Get the call count, we shouldn't close the sound device when there is
282 * any calls active.
283 */
284 call_cnt = pjsua_call_get_count();
285
286 /* When this function is called from pjsua_media_channel_deinit() upon
287 * disconnecting call, actually the call count hasn't been updated/
288 * decreased. So we put additional check here, if there is only one
289 * call and it's in DISCONNECTED state, there is actually no active
290 * call.
291 */
292 if (call_cnt == 1) {
293 pjsua_call_id call_id;
294 pj_status_t status;
295
296 status = pjsua_enum_calls(&call_id, &call_cnt);
297 if (status == PJ_SUCCESS && call_cnt > 0 &&
298 !pjsua_call_is_active(call_id))
299 {
300 call_cnt = 0;
301 }
302 }
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000303
304 /* Activate sound device auto-close timer if sound device is idle.
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000305 * It is idle when there is no port connection in the bridge and
306 * there is no active call.
Benny Prijono2d647722011-07-13 03:05:22 +0000307 *
308 * Note: this block is now valid if no snd dev is used because of #1299
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000309 */
Benny Prijono2d647722011-07-13 03:05:22 +0000310 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL ||
311 pjsua_var.no_snd) &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000312 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
313 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000314 call_cnt == 0 &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000315 pjsua_var.media_cfg.snd_auto_close_time >= 0)
316 {
317 pj_time_val delay;
318
319 delay.msec = 0;
320 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
321
322 pjsua_var.snd_idle_timer.id = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000323 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000324 &delay);
325 }
326}
327
328
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000329/* Timer callback to close sound device */
330static void close_snd_timer_cb( pj_timer_heap_t *th,
331 pj_timer_entry *entry)
332{
333 PJ_UNUSED_ARG(th);
334
Benny Prijono0f711b42009-05-06 19:08:43 +0000335 PJSUA_LOCK();
336 if (entry->id) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000337 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
Benny Prijono0f711b42009-05-06 19:08:43 +0000338 pjsua_var.media_cfg.snd_auto_close_time));
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000339
Benny Prijono0f711b42009-05-06 19:08:43 +0000340 entry->id = PJ_FALSE;
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000341
Benny Prijono0f711b42009-05-06 19:08:43 +0000342 close_snd_dev();
343 }
344 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000345}
346
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000347
348/*
349 * Start pjsua media subsystem.
350 */
351pj_status_t pjsua_media_subsys_start(void)
352{
353 pj_status_t status;
354
Benny Prijonob90fd382011-09-18 14:59:56 +0000355 pj_log_push_indent();
356
Benny Prijono0bc99a92011-03-17 04:34:43 +0000357#if DISABLED_FOR_TICKET_1185
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000358 /* Create media for calls, if none is specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000359 if (pjsua_var.calls[0].media[0].tp == NULL) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000360 pjsua_transport_config transport_cfg;
361
362 /* Create default transport config */
363 pjsua_transport_config_default(&transport_cfg);
364 transport_cfg.port = DEFAULT_RTP_PORT;
365
366 status = pjsua_media_transports_create(&transport_cfg);
Benny Prijonob90fd382011-09-18 14:59:56 +0000367 if (status != PJ_SUCCESS) {
368 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000369 return status;
Benny Prijonob90fd382011-09-18 14:59:56 +0000370 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000371 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000372#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000373
Benny Prijono0bc99a92011-03-17 04:34:43 +0000374 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000375 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000376
Benny Prijono9f468d12011-07-07 07:46:33 +0000377 /* Video */
378#if PJMEDIA_HAS_VIDEO
379 status = pjsua_vid_subsys_start();
Benny Prijonob90fd382011-09-18 14:59:56 +0000380 if (status != PJ_SUCCESS) {
381 pj_log_pop_indent();
Benny Prijono9f468d12011-07-07 07:46:33 +0000382 return status;
Benny Prijonob90fd382011-09-18 14:59:56 +0000383 }
Benny Prijono9f468d12011-07-07 07:46:33 +0000384#endif
385
Benny Prijonobf53b002010-01-04 13:08:31 +0000386 /* Perform NAT detection */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000387 status = pjsua_detect_nat_type();
388 if (status != PJ_SUCCESS) {
389 PJ_PERROR(1,(THIS_FILE, status, "NAT type detection failed"));
390 }
Benny Prijonobf53b002010-01-04 13:08:31 +0000391
Benny Prijonob90fd382011-09-18 14:59:56 +0000392 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000393 return PJ_SUCCESS;
394}
395
396
397/*
398 * Destroy pjsua media subsystem.
399 */
Sauw Minge7dbbc82011-10-24 09:28:13 +0000400pj_status_t pjsua_media_subsys_destroy(unsigned flags)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000401{
402 unsigned i;
403
Benny Prijono384dab42009-10-14 01:58:04 +0000404 PJ_LOG(4,(THIS_FILE, "Shutting down media.."));
Benny Prijonob90fd382011-09-18 14:59:56 +0000405 pj_log_push_indent();
Benny Prijono384dab42009-10-14 01:58:04 +0000406
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000407 close_snd_dev();
408
409 if (pjsua_var.mconf) {
410 pjmedia_conf_destroy(pjsua_var.mconf);
411 pjsua_var.mconf = NULL;
412 }
413
414 if (pjsua_var.null_port) {
415 pjmedia_port_destroy(pjsua_var.null_port);
416 pjsua_var.null_port = NULL;
417 }
418
419 /* Destroy file players */
420 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
421 if (pjsua_var.player[i].port) {
422 pjmedia_port_destroy(pjsua_var.player[i].port);
423 pjsua_var.player[i].port = NULL;
424 }
425 }
426
427 /* Destroy file recorders */
428 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
429 if (pjsua_var.recorder[i].port) {
430 pjmedia_port_destroy(pjsua_var.recorder[i].port);
431 pjsua_var.recorder[i].port = NULL;
432 }
433 }
434
435 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000436 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000437 unsigned strm_idx;
438 pjsua_call *call = &pjsua_var.calls[i];
439 for (strm_idx=0; strm_idx<call->med_cnt; ++strm_idx) {
440 pjsua_call_media *call_med = &call->media[strm_idx];
441 if (call_med->tp_st != PJSUA_MED_TP_IDLE) {
442 pjsua_media_channel_deinit(i);
443 }
444 if (call_med->tp && call_med->tp_auto_del) {
Sauw Minge7dbbc82011-10-24 09:28:13 +0000445 /* TODO: check if we're not allowed to send to network in the
446 * "flags", and if so do not do TURN allocation...
447 */
448 PJ_UNUSED_ARG(flags);
Benny Prijono0bc99a92011-03-17 04:34:43 +0000449 pjmedia_transport_close(call_med->tp);
450 }
451 call_med->tp = NULL;
Benny Prijono311b63f2008-07-14 11:31:40 +0000452 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000453 }
454
455 /* Destroy media endpoint. */
456 if (pjsua_var.med_endpt) {
457
Benny Prijono0bc99a92011-03-17 04:34:43 +0000458# if PJMEDIA_HAS_VIDEO
Benny Prijono9f468d12011-07-07 07:46:33 +0000459 pjsua_vid_subsys_destroy();
Benny Prijono0bc99a92011-03-17 04:34:43 +0000460# endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000461
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000462 pjmedia_endpt_destroy(pjsua_var.med_endpt);
463 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000464
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000465 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000466 // Not necessary, as pjmedia_snd_deinit() should have been called
467 // in pjmedia_endpt_destroy().
468 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000469 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000470
Benny Prijonode479562007-03-15 10:23:55 +0000471 /* Reset RTP port */
472 next_rtp_port = 0;
473
Benny Prijonob90fd382011-09-18 14:59:56 +0000474 pj_log_pop_indent();
475
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000476 return PJ_SUCCESS;
477}
478
Benny Prijono0bc99a92011-03-17 04:34:43 +0000479/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000480 * Create RTP and RTCP socket pair, and possibly resolve their public
481 * address via STUN.
482 */
483static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
484 pjmedia_sock_info *skinfo)
485{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000486 enum {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000487 RTP_RETRY = 100
488 };
489 int i;
490 pj_sockaddr_in bound_addr;
491 pj_sockaddr_in mapped_addr[2];
492 pj_status_t status = PJ_SUCCESS;
493 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
494 pj_sock_t sock[2];
495
496 /* Make sure STUN server resolution has completed */
497 status = resolve_stun_server(PJ_TRUE);
498 if (status != PJ_SUCCESS) {
499 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
500 return status;
501 }
502
503 if (next_rtp_port == 0)
504 next_rtp_port = (pj_uint16_t)cfg->port;
505
Benny Prijono0bc99a92011-03-17 04:34:43 +0000506 if (next_rtp_port == 0)
507 next_rtp_port = (pj_uint16_t)40000;
508
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000509 for (i=0; i<2; ++i)
510 sock[i] = PJ_INVALID_SOCKET;
511
512 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
513 if (cfg->bound_addr.slen) {
514 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
515 if (status != PJ_SUCCESS) {
516 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
517 status);
518 return status;
519 }
520 }
521
522 /* Loop retry to bind RTP and RTCP sockets. */
523 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
524
525 /* Create RTP socket. */
526 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
527 if (status != PJ_SUCCESS) {
528 pjsua_perror(THIS_FILE, "socket() error", status);
529 return status;
530 }
531
532 /* Apply QoS to RTP socket, if specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000533 status = pj_sock_apply_qos2(sock[0], cfg->qos_type,
534 &cfg->qos_params,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000535 2, THIS_FILE, "RTP socket");
536
537 /* Bind RTP socket */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000538 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000539 next_rtp_port);
540 if (status != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000541 pj_sock_close(sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000542 sock[0] = PJ_INVALID_SOCKET;
543 continue;
544 }
545
546 /* Create RTCP socket. */
547 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
548 if (status != PJ_SUCCESS) {
549 pjsua_perror(THIS_FILE, "socket() error", status);
550 pj_sock_close(sock[0]);
551 return status;
552 }
553
554 /* Apply QoS to RTCP socket, if specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000555 status = pj_sock_apply_qos2(sock[1], cfg->qos_type,
556 &cfg->qos_params,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000557 2, THIS_FILE, "RTCP socket");
558
559 /* Bind RTCP socket */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000560 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000561 (pj_uint16_t)(next_rtp_port+1));
562 if (status != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000563 pj_sock_close(sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000564 sock[0] = PJ_INVALID_SOCKET;
565
Benny Prijono0bc99a92011-03-17 04:34:43 +0000566 pj_sock_close(sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000567 sock[1] = PJ_INVALID_SOCKET;
568 continue;
569 }
570
571 /*
572 * If we're configured to use STUN, then find out the mapped address,
573 * and make sure that the mapped RTCP port is adjacent with the RTP.
574 */
575 if (pjsua_var.stun_srv.addr.sa_family != 0) {
576 char ip_addr[32];
577 pj_str_t stun_srv;
578
Benny Prijono0bc99a92011-03-17 04:34:43 +0000579 pj_ansi_strcpy(ip_addr,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000580 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
581 stun_srv = pj_str(ip_addr);
582
583 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
584 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
585 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
586 mapped_addr);
587 if (status != PJ_SUCCESS) {
588 pjsua_perror(THIS_FILE, "STUN resolve error", status);
589 goto on_error;
590 }
591
592#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijono0bc99a92011-03-17 04:34:43 +0000593 if (pj_ntohs(mapped_addr[1].sin_port) ==
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000594 pj_ntohs(mapped_addr[0].sin_port)+1)
595 {
596 /* Success! */
597 break;
598 }
599
Benny Prijono0bc99a92011-03-17 04:34:43 +0000600 pj_sock_close(sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000601 sock[0] = PJ_INVALID_SOCKET;
602
Benny Prijono0bc99a92011-03-17 04:34:43 +0000603 pj_sock_close(sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000604 sock[1] = PJ_INVALID_SOCKET;
605#else
Benny Prijono0bc99a92011-03-17 04:34:43 +0000606 if (pj_ntohs(mapped_addr[1].sin_port) !=
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000607 pj_ntohs(mapped_addr[0].sin_port)+1)
608 {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000609 PJ_LOG(4,(THIS_FILE,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000610 "Note: STUN mapped RTCP port %d is not adjacent"
611 " to RTP port %d",
612 pj_ntohs(mapped_addr[1].sin_port),
613 pj_ntohs(mapped_addr[0].sin_port)));
614 }
615 /* Success! */
616 break;
617#endif
618
619 } else if (cfg->public_addr.slen) {
620
621 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
622 (pj_uint16_t)next_rtp_port);
623 if (status != PJ_SUCCESS)
624 goto on_error;
625
626 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
627 (pj_uint16_t)(next_rtp_port+1));
628 if (status != PJ_SUCCESS)
629 goto on_error;
630
631 break;
632
633 } else {
634
635 if (bound_addr.sin_addr.s_addr == 0) {
636 pj_sockaddr addr;
637
638 /* Get local IP address. */
639 status = pj_gethostip(pj_AF_INET(), &addr);
640 if (status != PJ_SUCCESS)
641 goto on_error;
642
643 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
644 }
645
646 for (i=0; i<2; ++i) {
647 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
648 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
649 }
650
651 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
652 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
653 break;
654 }
655 }
656
657 if (sock[0] == PJ_INVALID_SOCKET) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000658 PJ_LOG(1,(THIS_FILE,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000659 "Unable to find appropriate RTP/RTCP ports combination"));
660 goto on_error;
661 }
662
663
664 skinfo->rtp_sock = sock[0];
Benny Prijono0bc99a92011-03-17 04:34:43 +0000665 pj_memcpy(&skinfo->rtp_addr_name,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000666 &mapped_addr[0], sizeof(pj_sockaddr_in));
667
668 skinfo->rtcp_sock = sock[1];
Benny Prijono0bc99a92011-03-17 04:34:43 +0000669 pj_memcpy(&skinfo->rtcp_addr_name,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000670 &mapped_addr[1], sizeof(pj_sockaddr_in));
671
672 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
673 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
674 sizeof(addr_buf), 3)));
675 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
676 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
677 sizeof(addr_buf), 3)));
678
679 next_rtp_port += 2;
680 return PJ_SUCCESS;
681
682on_error:
683 for (i=0; i<2; ++i) {
684 if (sock[i] != PJ_INVALID_SOCKET)
685 pj_sock_close(sock[i]);
686 }
687 return status;
688}
689
Benny Prijono0bc99a92011-03-17 04:34:43 +0000690/* Create normal UDP media transports */
691static pj_status_t create_udp_media_transport(const pjsua_transport_config *cfg,
692 pjsua_call_media *call_med)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000693{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000694 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000695 pj_status_t status;
696
Benny Prijono0bc99a92011-03-17 04:34:43 +0000697 status = create_rtp_rtcp_sock(cfg, &skinfo);
698 if (status != PJ_SUCCESS) {
699 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
700 status);
701 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000702 }
703
Benny Prijono0bc99a92011-03-17 04:34:43 +0000704 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
705 &skinfo, 0, &call_med->tp);
706 if (status != PJ_SUCCESS) {
707 pjsua_perror(THIS_FILE, "Unable to create media transport",
708 status);
709 goto on_error;
710 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000711
Benny Prijono0bc99a92011-03-17 04:34:43 +0000712 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
713 pjsua_var.media_cfg.tx_drop_pct);
714
715 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
716 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000717
Sauw Ming73ecfe82011-09-21 10:20:01 +0000718 call_med->tp_ready = PJ_SUCCESS;
719
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000720 return PJ_SUCCESS;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000721
722on_error:
723 if (call_med->tp)
724 pjmedia_transport_close(call_med->tp);
725
726 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000727}
728
Benny Prijono0bc99a92011-03-17 04:34:43 +0000729#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +0000730/* Create normal UDP media transports */
731static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000732{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000733 unsigned i;
734 pj_status_t status;
735
Benny Prijono0bc99a92011-03-17 04:34:43 +0000736 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
737 pjsua_call *call = &pjsua_var.calls[i];
738 unsigned strm_idx;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000739
Benny Prijono0bc99a92011-03-17 04:34:43 +0000740 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
741 pjsua_call_media *call_med = &call->media[strm_idx];
742
743 status = create_udp_media_transport(cfg, &call_med->tp);
744 if (status != PJ_SUCCESS)
745 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000746 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000747 }
748
Benny Prijonoc97608e2007-03-23 16:34:20 +0000749 return PJ_SUCCESS;
750
751on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000752 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
753 pjsua_call *call = &pjsua_var.calls[i];
754 unsigned strm_idx;
755
756 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
757 pjsua_call_media *call_med = &call->media[strm_idx];
758
759 if (call_med->tp) {
760 pjmedia_transport_close(call_med->tp);
761 call_med->tp = NULL;
762 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000763 }
764 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000765 return status;
766}
Benny Prijono0bc99a92011-03-17 04:34:43 +0000767#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000768
Sauw Ming2a899232012-01-09 11:51:56 +0000769static void med_tp_timer_cb(void *user_data)
770{
771 pjsua_call_media *call_med = (pjsua_call_media*)user_data;
772
773 PJSUA_LOCK();
774
775 call_med->tp_ready = call_med->tp_result;
776 if (call_med->med_create_cb)
777 (*call_med->med_create_cb)(call_med, call_med->tp_ready,
778 call_med->call->secure_level, NULL);
779
780 PJSUA_UNLOCK();
781}
782
Benny Prijono096c56c2007-09-15 08:30:16 +0000783/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000784static void on_ice_complete(pjmedia_transport *tp,
785 pj_ice_strans_op op,
786 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000787{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000788 pjsua_call_media *call_med = (pjsua_call_media*)tp->user_data;
Benny Prijono096c56c2007-09-15 08:30:16 +0000789
Benny Prijono0bc99a92011-03-17 04:34:43 +0000790 if (!call_med)
Benny Prijonof76e1392008-06-06 14:51:48 +0000791 return;
792
793 switch (op) {
794 case PJ_ICE_STRANS_OP_INIT:
Sauw Ming2a899232012-01-09 11:51:56 +0000795 call_med->tp_result = result;
796 pjsua_schedule_timer2(&med_tp_timer_cb, call_med, 1);
Benny Prijonof76e1392008-06-06 14:51:48 +0000797 break;
798 case PJ_ICE_STRANS_OP_NEGOTIATION:
799 if (result != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000800 call_med->state = PJSUA_CALL_MEDIA_ERROR;
801 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijonof76e1392008-06-06 14:51:48 +0000802
Benny Prijono0bc99a92011-03-17 04:34:43 +0000803 if (call_med->call && pjsua_var.ua_cfg.cb.on_call_media_state) {
804 pjsua_var.ua_cfg.cb.on_call_media_state(call_med->call->index);
Benny Prijonof76e1392008-06-06 14:51:48 +0000805 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000806 } else if (call_med->call) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000807 /* Send UPDATE if default transport address is different than
808 * what was advertised (ticket #881)
809 */
810 pjmedia_transport_info tpinfo;
811 pjmedia_ice_transport_info *ii = NULL;
812 unsigned i;
813
814 pjmedia_transport_info_init(&tpinfo);
815 pjmedia_transport_get_info(tp, &tpinfo);
816 for (i=0; i<tpinfo.specific_info_cnt; ++i) {
817 if (tpinfo.spc_info[i].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
818 ii = (pjmedia_ice_transport_info*)
819 tpinfo.spc_info[i].buffer;
820 break;
821 }
822 }
823
824 if (ii && ii->role==PJ_ICE_SESS_ROLE_CONTROLLING &&
825 pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000826 &call_med->rtp_addr))
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000827 {
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000828 pj_bool_t use_update;
829 const pj_str_t STR_UPDATE = { "UPDATE", 6 };
830 pjsip_dialog_cap_status support_update;
831 pjsip_dialog *dlg;
832
Benny Prijono0bc99a92011-03-17 04:34:43 +0000833 dlg = call_med->call->inv->dlg;
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000834 support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW,
835 NULL, &STR_UPDATE);
836 use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED);
837
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000838 PJ_LOG(4,(THIS_FILE,
839 "ICE default transport address has changed for "
Benny Prijonodb5d89d2011-10-25 13:39:06 +0000840 "call %d, sending %s",
841 call_med->call->index,
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000842 (use_update ? "UPDATE" : "re-INVITE")));
843
844 if (use_update)
Benny Prijono0bc99a92011-03-17 04:34:43 +0000845 pjsua_call_update(call_med->call->index, 0, NULL);
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000846 else
Benny Prijono0bc99a92011-03-17 04:34:43 +0000847 pjsua_call_reinvite(call_med->call->index, 0, NULL);
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000848 }
Benny Prijonof76e1392008-06-06 14:51:48 +0000849 }
850 break;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000851 case PJ_ICE_STRANS_OP_KEEP_ALIVE:
852 if (result != PJ_SUCCESS) {
853 PJ_PERROR(4,(THIS_FILE, result,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000854 "ICE keep alive failure for transport %d:%d",
855 call_med->call->index, call_med->idx));
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000856 }
Sauw Ming73ecfe82011-09-21 10:20:01 +0000857 if (pjsua_var.ua_cfg.cb.on_call_media_transport_state) {
858 pjsua_med_tp_state_info info;
859
860 pj_bzero(&info, sizeof(info));
861 info.med_idx = call_med->idx;
862 info.state = call_med->tp_st;
863 info.status = result;
864 info.ext_info = &op;
865 (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)(
866 call_med->call->index, &info);
867 }
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000868 if (pjsua_var.ua_cfg.cb.on_ice_transport_error) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000869 pjsua_call_id id = call_med->call->index;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000870 (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result,
871 NULL);
872 }
873 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000874 }
875}
876
877
Benny Prijonof76e1392008-06-06 14:51:48 +0000878/* Parse "HOST:PORT" format */
879static pj_status_t parse_host_port(const pj_str_t *host_port,
880 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000881{
Benny Prijonof76e1392008-06-06 14:51:48 +0000882 pj_str_t str_port;
883
884 str_port.ptr = pj_strchr(host_port, ':');
885 if (str_port.ptr != NULL) {
886 int iport;
887
888 host->ptr = host_port->ptr;
889 host->slen = (str_port.ptr - host->ptr);
890 str_port.ptr++;
891 str_port.slen = host_port->slen - host->slen - 1;
892 iport = (int)pj_strtoul(&str_port);
893 if (iport < 1 || iport > 65535)
894 return PJ_EINVAL;
895 *port = (pj_uint16_t)iport;
896 } else {
897 *host = *host_port;
898 *port = 0;
899 }
900
901 return PJ_SUCCESS;
902}
903
904/* Create ICE media transports (when ice is enabled) */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000905static pj_status_t create_ice_media_transport(
906 const pjsua_transport_config *cfg,
Sauw Ming73ecfe82011-09-21 10:20:01 +0000907 pjsua_call_media *call_med,
908 pj_bool_t async)
Benny Prijonof76e1392008-06-06 14:51:48 +0000909{
910 char stunip[PJ_INET6_ADDRSTRLEN];
911 pj_ice_strans_cfg ice_cfg;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000912 pjmedia_ice_cb ice_cb;
913 char name[32];
914 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000915 pj_status_t status;
916
Benny Prijonoda9785b2007-04-02 20:43:06 +0000917 /* Make sure STUN server resolution has completed */
Benny Prijonobb995fd2009-08-12 11:03:23 +0000918 status = resolve_stun_server(PJ_TRUE);
Benny Prijonoda9785b2007-04-02 20:43:06 +0000919 if (status != PJ_SUCCESS) {
920 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
921 return status;
922 }
923
Benny Prijonof76e1392008-06-06 14:51:48 +0000924 /* Create ICE stream transport configuration */
925 pj_ice_strans_cfg_default(&ice_cfg);
926 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
927 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
928 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
929
930 ice_cfg.af = pj_AF_INET();
931 ice_cfg.resolver = pjsua_var.resolver;
932
Benny Prijono329d6382009-05-29 13:04:03 +0000933 ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
934
Benny Prijonof76e1392008-06-06 14:51:48 +0000935 /* Configure STUN settings */
936 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
937 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
938 ice_cfg.stun.server = pj_str(stunip);
939 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
940 }
Benny Prijono329d6382009-05-29 13:04:03 +0000941 if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
942 ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
Benny Prijonof76e1392008-06-06 14:51:48 +0000943
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000944 /* Copy QoS setting to STUN setting */
945 ice_cfg.stun.cfg.qos_type = cfg->qos_type;
946 pj_memcpy(&ice_cfg.stun.cfg.qos_params, &cfg->qos_params,
947 sizeof(cfg->qos_params));
948
Benny Prijonof76e1392008-06-06 14:51:48 +0000949 /* Configure TURN settings */
950 if (pjsua_var.media_cfg.enable_turn) {
951 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
952 &ice_cfg.turn.server,
953 &ice_cfg.turn.port);
954 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
955 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
956 return PJ_EINVAL;
957 }
958 if (ice_cfg.turn.port == 0)
959 ice_cfg.turn.port = 3479;
960 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
961 pj_memcpy(&ice_cfg.turn.auth_cred,
962 &pjsua_var.media_cfg.turn_auth_cred,
963 sizeof(ice_cfg.turn.auth_cred));
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000964
965 /* Copy QoS setting to TURN setting */
966 ice_cfg.turn.cfg.qos_type = cfg->qos_type;
967 pj_memcpy(&ice_cfg.turn.cfg.qos_params, &cfg->qos_params,
968 sizeof(cfg->qos_params));
Benny Prijonof76e1392008-06-06 14:51:48 +0000969 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000970
Benny Prijono0bc99a92011-03-17 04:34:43 +0000971 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
972 ice_cb.on_ice_complete = &on_ice_complete;
973 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", call_med->idx);
974 call_med->tp_ready = PJ_EPENDING;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000975
Benny Prijono0bc99a92011-03-17 04:34:43 +0000976 comp_cnt = 1;
977 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
978 ++comp_cnt;
Benny Prijonof76e1392008-06-06 14:51:48 +0000979
Benny Prijonobd6613f2011-04-11 17:27:14 +0000980 status = pjmedia_ice_create3(pjsua_var.med_endpt, name, comp_cnt,
981 &ice_cfg, &ice_cb, 0, call_med,
982 &call_med->tp);
Benny Prijono0bc99a92011-03-17 04:34:43 +0000983 if (status != PJ_SUCCESS) {
984 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
985 status);
986 goto on_error;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000987 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000988
Benny Prijono0bc99a92011-03-17 04:34:43 +0000989 /* Wait until transport is initialized, or time out */
Sauw Ming73ecfe82011-09-21 10:20:01 +0000990 if (!async) {
Nanang Izzuddin1c565422011-12-28 09:52:07 +0000991 pj_bool_t has_pjsua_lock = PJSUA_LOCK_IS_LOCKED();
992 if (has_pjsua_lock)
993 PJSUA_UNLOCK();
Sauw Ming73ecfe82011-09-21 10:20:01 +0000994 while (call_med->tp_ready == PJ_EPENDING) {
995 pjsua_handle_events(100);
996 }
Nanang Izzuddin1c565422011-12-28 09:52:07 +0000997 if (has_pjsua_lock)
998 PJSUA_LOCK();
Benny Prijono0bc99a92011-03-17 04:34:43 +0000999 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001000
1001 if (async && call_med->tp_ready == PJ_EPENDING) {
1002 return PJ_EPENDING;
1003 } else if (call_med->tp_ready != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001004 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
1005 call_med->tp_ready);
1006 status = call_med->tp_ready;
1007 goto on_error;
1008 }
1009
1010 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
1011 pjsua_var.media_cfg.tx_drop_pct);
1012
1013 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
1014 pjsua_var.media_cfg.rx_drop_pct);
1015
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001016 return PJ_SUCCESS;
1017
1018on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +00001019 if (call_med->tp != NULL) {
1020 pjmedia_transport_close(call_med->tp);
1021 call_med->tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001022 }
1023
Benny Prijonoc97608e2007-03-23 16:34:20 +00001024 return status;
1025}
1026
Benny Prijono0bc99a92011-03-17 04:34:43 +00001027#if DISABLED_FOR_TICKET_1185
1028/* Create ICE media transports (when ice is enabled) */
1029static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
1030{
1031 unsigned i;
1032 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001033
Benny Prijono0bc99a92011-03-17 04:34:43 +00001034 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
1035 pjsua_call *call = &pjsua_var.calls[i];
1036 unsigned strm_idx;
1037
1038 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1039 pjsua_call_media *call_med = &call->media[strm_idx];
1040
1041 status = create_ice_media_transport(cfg, call_med);
1042 if (status != PJ_SUCCESS)
1043 goto on_error;
1044 }
1045 }
1046
1047 return PJ_SUCCESS;
1048
1049on_error:
1050 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
1051 pjsua_call *call = &pjsua_var.calls[i];
1052 unsigned strm_idx;
1053
1054 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1055 pjsua_call_media *call_med = &call->media[strm_idx];
1056
1057 if (call_med->tp) {
1058 pjmedia_transport_close(call_med->tp);
1059 call_med->tp = NULL;
1060 }
1061 }
1062 }
1063 return status;
1064}
1065#endif
1066
1067#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +00001068/*
Benny Prijono0bc99a92011-03-17 04:34:43 +00001069 * Create media transports for all the calls. This function creates
Benny Prijonoc97608e2007-03-23 16:34:20 +00001070 * one UDP media transport for each call.
1071 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001072PJ_DEF(pj_status_t) pjsua_media_transports_create(
1073 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001074{
1075 pjsua_transport_config cfg;
1076 unsigned i;
1077 pj_status_t status;
1078
1079
1080 /* Make sure pjsua_init() has been called */
1081 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
1082
1083 PJSUA_LOCK();
1084
1085 /* Delete existing media transports */
1086 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001087 pjsua_call *call = &pjsua_var.calls[i];
1088 unsigned strm_idx;
1089
1090 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1091 pjsua_call_media *call_med = &call->media[strm_idx];
1092
1093 if (call_med->tp && call_med->tp_auto_del) {
1094 pjmedia_transport_close(call_med->tp);
1095 call_med->tp = NULL;
1096 call_med->tp_orig = NULL;
1097 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001098 }
1099 }
1100
1101 /* Copy config */
1102 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
1103
Benny Prijono40860c32008-09-04 13:55:33 +00001104 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001105 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijono4d79b0f2009-10-25 09:02:07 +00001106 status = create_ice_media_transports(&cfg);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001107 } else {
1108 status = create_udp_media_transports(&cfg);
1109 }
1110
Benny Prijono40860c32008-09-04 13:55:33 +00001111 /* Set media transport auto_delete to True */
1112 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001113 pjsua_call *call = &pjsua_var.calls[i];
1114 unsigned strm_idx;
1115
1116 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1117 pjsua_call_media *call_med = &call->media[strm_idx];
1118
1119 call_med->tp_auto_del = PJ_TRUE;
1120 }
Benny Prijono40860c32008-09-04 13:55:33 +00001121 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001122
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001123 PJSUA_UNLOCK();
1124
1125 return status;
1126}
1127
Benny Prijono40860c32008-09-04 13:55:33 +00001128/*
1129 * Attach application's created media transports.
1130 */
1131PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
1132 unsigned count,
1133 pj_bool_t auto_delete)
1134{
1135 unsigned i;
1136
1137 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
1138
1139 /* Assign the media transports */
1140 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001141 pjsua_call *call = &pjsua_var.calls[i];
1142 unsigned strm_idx;
1143
1144 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1145 pjsua_call_media *call_med = &call->media[strm_idx];
1146
1147 if (call_med->tp && call_med->tp_auto_del) {
1148 pjmedia_transport_close(call_med->tp);
1149 call_med->tp = NULL;
1150 call_med->tp_orig = NULL;
1151 }
Benny Prijono40860c32008-09-04 13:55:33 +00001152 }
1153
Benny Prijono0bc99a92011-03-17 04:34:43 +00001154 PJ_TODO(remove_pjsua_media_transports_attach);
1155
1156 call->media[0].tp = tp[i].transport;
1157 call->media[0].tp_auto_del = auto_delete;
Benny Prijono40860c32008-09-04 13:55:33 +00001158 }
1159
1160 return PJ_SUCCESS;
1161}
Benny Prijono0bc99a92011-03-17 04:34:43 +00001162#endif
Benny Prijono40860c32008-09-04 13:55:33 +00001163
Benny Prijono0bc99a92011-03-17 04:34:43 +00001164/* Go through the list of media in the SDP, find acceptable media, and
1165 * sort them based on the "quality" of the media, and store the indexes
1166 * in the specified array. Media with the best quality will be listed
1167 * first in the array. The quality factors considered currently is
1168 * encryption.
1169 */
1170static void sort_media(const pjmedia_sdp_session *sdp,
1171 const pj_str_t *type,
1172 pjmedia_srtp_use use_srtp,
1173 pj_uint8_t midx[],
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001174 unsigned *p_count,
1175 unsigned *p_total_count)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001176{
1177 unsigned i;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001178 unsigned count = 0;
1179 int score[PJSUA_MAX_CALL_MEDIA];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001180
Benny Prijono0bc99a92011-03-17 04:34:43 +00001181 pj_assert(*p_count >= PJSUA_MAX_CALL_MEDIA);
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001182 pj_assert(*p_total_count >= PJSUA_MAX_CALL_MEDIA);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001183
1184 *p_count = 0;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001185 *p_total_count = 0;
Benny Prijono09c0d672011-04-11 05:03:24 +00001186 for (i=0; i<PJSUA_MAX_CALL_MEDIA; ++i)
1187 score[i] = 1;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001188
1189 /* Score each media */
1190 for (i=0; i<sdp->media_count && count<PJSUA_MAX_CALL_MEDIA; ++i) {
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001191 const pjmedia_sdp_media *m = sdp->media[i];
Nanang Izzuddina6414292011-04-08 04:26:18 +00001192 const pjmedia_sdp_conn *c;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001193
Benny Prijono0bc99a92011-03-17 04:34:43 +00001194 /* Skip different media */
1195 if (pj_stricmp(&m->desc.media, type) != 0) {
1196 score[count++] = -22000;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001197 continue;
1198 }
1199
Nanang Izzuddina6414292011-04-08 04:26:18 +00001200 c = m->conn? m->conn : sdp->conn;
1201
Benny Prijono0bc99a92011-03-17 04:34:43 +00001202 /* Supported transports */
1203 if (pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0) {
1204 switch (use_srtp) {
1205 case PJMEDIA_SRTP_MANDATORY:
1206 case PJMEDIA_SRTP_OPTIONAL:
1207 ++score[i];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001208 break;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001209 case PJMEDIA_SRTP_DISABLED:
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001210 //--score[i];
1211 score[i] -= 5;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001212 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001213 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001214 } else if (pj_stricmp2(&m->desc.transport, "RTP/AVP")==0) {
1215 switch (use_srtp) {
1216 case PJMEDIA_SRTP_MANDATORY:
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001217 //--score[i];
1218 score[i] -= 5;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001219 break;
1220 case PJMEDIA_SRTP_OPTIONAL:
1221 /* No change in score */
1222 break;
1223 case PJMEDIA_SRTP_DISABLED:
1224 ++score[i];
1225 break;
1226 }
1227 } else {
1228 score[i] -= 10;
1229 }
1230
1231 /* Is media disabled? */
1232 if (m->desc.port == 0)
1233 score[i] -= 10;
1234
1235 /* Is media inactive? */
Nanang Izzuddina6414292011-04-08 04:26:18 +00001236 if (pjmedia_sdp_media_find_attr2(m, "inactive", NULL) ||
1237 pj_strcmp2(&c->addr, "0.0.0.0") == 0)
1238 {
1239 //score[i] -= 10;
1240 score[i] -= 1;
1241 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001242
1243 ++count;
1244 }
1245
1246 /* Created sorted list based on quality */
1247 for (i=0; i<count; ++i) {
1248 unsigned j;
1249 int best = 0;
1250
1251 for (j=1; j<count; ++j) {
1252 if (score[j] > score[best])
1253 best = j;
1254 }
1255 /* Don't put media with negative score, that media is unacceptable
1256 * for us.
1257 */
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001258 midx[i] = (pj_uint8_t)best;
1259 if (score[best] >= 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00001260 (*p_count)++;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001261 if (score[best] > -22000)
1262 (*p_total_count)++;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001263
1264 score[best] = -22000;
1265
1266 }
1267}
1268
Benny Prijonoee0ba182011-07-15 06:18:29 +00001269/* Callback to receive media events */
Sauw Ming0fabe1b2011-12-01 10:49:07 +00001270pj_status_t call_media_on_event(pjmedia_event *event,
1271 void *user_data)
Benny Prijonoee0ba182011-07-15 06:18:29 +00001272{
Sauw Ming0fabe1b2011-12-01 10:49:07 +00001273 pjsua_call_media *call_med = (pjsua_call_media*)user_data;
Benny Prijonoee0ba182011-07-15 06:18:29 +00001274 pjsua_call *call = call_med->call;
Nanang Izzuddin3da4ad82011-12-07 10:43:28 +00001275 pj_status_t status = PJ_SUCCESS;
1276
1277 switch(event->type) {
1278 case PJMEDIA_EVENT_KEYFRAME_MISSING:
1279 if (call->opt.req_keyframe_method & PJSUA_VID_REQ_KEYFRAME_SIP_INFO)
1280 {
1281 pj_timestamp now;
1282
1283 pj_get_timestamp(&now);
1284 if (pj_elapsed_msec(&call_med->last_req_keyframe, &now) >=
1285 PJSUA_VID_REQ_KEYFRAME_INTERVAL)
1286 {
1287 pjsua_msg_data msg_data;
1288 const pj_str_t SIP_INFO = {"INFO", 4};
1289 const char *BODY_TYPE = "application/media_control+xml";
1290 const char *BODY =
1291 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
1292 "<media_control><vc_primitive><to_encoder>"
1293 "<picture_fast_update/>"
1294 "</to_encoder></vc_primitive></media_control>";
1295
1296 PJ_LOG(4,(THIS_FILE,
1297 "Sending video keyframe request via SIP INFO"));
1298
1299 pjsua_msg_data_init(&msg_data);
1300 pj_cstr(&msg_data.content_type, BODY_TYPE);
1301 pj_cstr(&msg_data.msg_body, BODY);
1302 status = pjsua_call_send_request(call->index, &SIP_INFO,
1303 &msg_data);
1304 if (status != PJ_SUCCESS) {
1305 pj_perror(3, THIS_FILE, status,
1306 "Failed requesting keyframe via SIP INFO");
1307 } else {
1308 call_med->last_req_keyframe = now;
1309 }
1310 }
1311 }
1312 break;
1313
1314 default:
1315 break;
1316 }
Benny Prijonoee0ba182011-07-15 06:18:29 +00001317
1318 if (pjsua_var.ua_cfg.cb.on_call_media_event && call) {
Benny Prijonoee0ba182011-07-15 06:18:29 +00001319 (*pjsua_var.ua_cfg.cb.on_call_media_event)(call->index,
1320 call_med->idx, event);
Benny Prijono53a7c702008-04-14 02:57:29 +00001321 }
1322
Nanang Izzuddin3da4ad82011-12-07 10:43:28 +00001323 return status;
Benny Prijono53a7c702008-04-14 02:57:29 +00001324}
1325
Sauw Ming73ecfe82011-09-21 10:20:01 +00001326/* Set media transport state and notify the application via the callback. */
1327void set_media_tp_state(pjsua_call_media *call_med,
1328 pjsua_med_tp_st tp_st)
1329{
1330 if (pjsua_var.ua_cfg.cb.on_call_media_transport_state &&
1331 call_med->tp_st != tp_st)
1332 {
1333 pjsua_med_tp_state_info info;
1334
1335 pj_bzero(&info, sizeof(info));
1336 info.med_idx = call_med->idx;
1337 info.state = tp_st;
1338 info.status = call_med->tp_ready;
1339 (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)(
1340 call_med->call->index, &info);
1341 }
1342
1343 call_med->tp_st = tp_st;
1344}
1345
1346/* Callback to resume pjsua_call_media_init() after media transport
1347 * creation is completed.
1348 */
1349static pj_status_t call_media_init_cb(pjsua_call_media *call_med,
1350 pj_status_t status,
1351 int security_level,
1352 int *sip_err_code)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001353{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001354 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
Benny Prijonodb5d89d2011-10-25 13:39:06 +00001355 pjmedia_transport_info tpinfo;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001356 int err_code = 0;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001357
Sauw Ming73ecfe82011-09-21 10:20:01 +00001358 if (status != PJ_SUCCESS)
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001359 goto on_return;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001360
Sauw Ming73ecfe82011-09-21 10:20:01 +00001361 if (call_med->tp_st == PJSUA_MED_TP_CREATING)
1362 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001363
Sauw Minge7dbbc82011-10-24 09:28:13 +00001364 if (!call_med->tp_orig &&
1365 pjsua_var.ua_cfg.cb.on_create_media_transport)
1366 {
1367 call_med->use_custom_med_tp = PJ_TRUE;
1368 } else
1369 call_med->use_custom_med_tp = PJ_FALSE;
1370
Benny Prijono0bc99a92011-03-17 04:34:43 +00001371#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1372 /* This function may be called when SRTP transport already exists
1373 * (e.g: in re-invite, update), don't need to destroy/re-create.
1374 */
Sauw Minge7dbbc82011-10-24 09:28:13 +00001375 if (!call_med->tp_orig) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001376 pjmedia_srtp_setting srtp_opt;
1377 pjmedia_transport *srtp = NULL;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001378
Benny Prijono0bc99a92011-03-17 04:34:43 +00001379 /* Check if SRTP requires secure signaling */
1380 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
1381 if (security_level < acc->cfg.srtp_secure_signaling) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001382 err_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001383 status = PJSIP_ESESSIONINSECURE;
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001384 goto on_return;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001385 }
1386 }
1387
1388 /* Always create SRTP adapter */
1389 pjmedia_srtp_setting_default(&srtp_opt);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001390 srtp_opt.close_member_tp = PJ_TRUE;
Sauw Minge7dbbc82011-10-24 09:28:13 +00001391 /* If media session has been ever established, let's use remote's
Benny Prijono0bc99a92011-03-17 04:34:43 +00001392 * preference in SRTP usage policy, especially when it is stricter.
1393 */
1394 if (call_med->rem_srtp_use > acc->cfg.use_srtp)
1395 srtp_opt.use = call_med->rem_srtp_use;
1396 else
1397 srtp_opt.use = acc->cfg.use_srtp;
1398
1399 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
1400 call_med->tp,
1401 &srtp_opt, &srtp);
1402 if (status != PJ_SUCCESS) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001403 err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001404 goto on_return;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001405 }
1406
1407 /* Set SRTP as current media transport */
1408 call_med->tp_orig = call_med->tp;
1409 call_med->tp = srtp;
1410 }
1411#else
Benny Prijono7df19342011-07-23 02:54:03 +00001412 call_med->tp_orig = call_med->tp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001413 PJ_UNUSED_ARG(security_level);
1414#endif
1415
Benny Prijonodb5d89d2011-10-25 13:39:06 +00001416
1417 pjmedia_transport_info_init(&tpinfo);
1418 pjmedia_transport_get_info(call_med->tp, &tpinfo);
1419
1420 pj_sockaddr_cp(&call_med->rtp_addr, &tpinfo.sock_info.rtp_addr_name);
1421
1422
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001423on_return:
Sauw Ming73ecfe82011-09-21 10:20:01 +00001424 if (status != PJ_SUCCESS && call_med->tp) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001425 pjmedia_transport_close(call_med->tp);
1426 call_med->tp = NULL;
1427 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001428
1429 if (sip_err_code)
1430 *sip_err_code = err_code;
1431
1432 if (call_med->med_init_cb) {
1433 pjsua_med_tp_state_info info;
1434
1435 pj_bzero(&info, sizeof(info));
1436 info.status = status;
1437 info.state = call_med->tp_st;
1438 info.med_idx = call_med->idx;
1439 info.sip_err_code = err_code;
1440 (*call_med->med_init_cb)(call_med->call->index, &info);
1441 }
1442
1443 return status;
1444}
1445
1446/* Initialize the media line */
1447pj_status_t pjsua_call_media_init(pjsua_call_media *call_med,
1448 pjmedia_type type,
1449 const pjsua_transport_config *tcfg,
1450 int security_level,
1451 int *sip_err_code,
1452 pj_bool_t async,
1453 pjsua_med_tp_state_cb cb)
1454{
Sauw Ming73ecfe82011-09-21 10:20:01 +00001455 pj_status_t status = PJ_SUCCESS;
1456
1457 /*
1458 * Note: this function may be called when the media already exists
1459 * (e.g. in reinvites, updates, etc.)
1460 */
1461 call_med->type = type;
1462
1463 /* Create the media transport for initial call. */
1464 if (call_med->tp == NULL) {
1465#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Nanang Izzuddin3d7385c2011-12-01 10:02:54 +00001466 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
1467
Sauw Ming73ecfe82011-09-21 10:20:01 +00001468 /* While in initial call, set default video devices */
1469 if (type == PJMEDIA_TYPE_VIDEO) {
1470 call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev;
1471 call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev;
1472 if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) {
1473 pjmedia_vid_dev_info info;
1474 pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info);
1475 call_med->strm.v.rdr_dev = info.id;
1476 }
1477 if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
1478 pjmedia_vid_dev_info info;
1479 pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info);
1480 call_med->strm.v.cap_dev = info.id;
1481 }
1482 }
1483#endif
1484
1485 set_media_tp_state(call_med, PJSUA_MED_TP_CREATING);
1486
Sauw Ming73ecfe82011-09-21 10:20:01 +00001487 if (pjsua_var.media_cfg.enable_ice) {
1488 status = create_ice_media_transport(tcfg, call_med, async);
Sauw Mingc8e12942011-10-25 08:51:02 +00001489 if (async && status == PJ_EPENDING) {
Sauw Ming848742f2011-09-28 04:20:30 +00001490 /* We will resume call media initialization in the
1491 * on_ice_complete() callback.
1492 */
Sauw Mingc8e12942011-10-25 08:51:02 +00001493 call_med->med_create_cb = &call_media_init_cb;
1494 call_med->med_init_cb = cb;
1495
Sauw Ming848742f2011-09-28 04:20:30 +00001496 return PJ_EPENDING;
1497 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001498 } else {
1499 status = create_udp_media_transport(tcfg, call_med);
1500 }
1501
Sauw Ming848742f2011-09-28 04:20:30 +00001502 if (status != PJ_SUCCESS) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001503 PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport"));
1504 return status;
1505 }
1506
1507 /* Media transport creation completed immediately, so
1508 * we don't need to call the callback.
1509 */
1510 call_med->med_init_cb = NULL;
1511
1512 } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) {
1513 /* Media is being reenabled. */
1514 set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
1515 }
1516
1517 return call_media_init_cb(call_med, status, security_level,
1518 sip_err_code);
1519}
1520
1521/* Callback to resume pjsua_media_channel_init() after media transport
1522 * initialization is completed.
1523 */
1524static pj_status_t media_channel_init_cb(pjsua_call_id call_id,
1525 const pjsua_med_tp_state_info *info)
1526{
1527 pjsua_call *call = &pjsua_var.calls[call_id];
1528 pj_status_t status = (info? info->status : PJ_SUCCESS);
1529 unsigned mi;
1530
1531 if (info) {
1532 pj_mutex_lock(call->med_ch_mutex);
1533
1534 /* Set the callback to NULL to indicate that the async operation
1535 * has completed.
1536 */
1537 call->media[info->med_idx].med_init_cb = NULL;
1538
1539 /* In case of failure, save the information to be returned
1540 * by the last media transport to finish.
1541 */
1542 if (info->status != PJ_SUCCESS)
1543 pj_memcpy(&call->med_ch_info, info, sizeof(info));
1544
1545 /* Check whether all the call's medias have finished calling their
1546 * callbacks.
1547 */
1548 for (mi=0; mi < call->med_cnt; ++mi) {
1549 pjsua_call_media *call_med = &call->media[mi];
1550
Sauw Ming2a899232012-01-09 11:51:56 +00001551 if (call_med->med_init_cb) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001552 pj_mutex_unlock(call->med_ch_mutex);
1553 return PJ_SUCCESS;
1554 }
1555
1556 if (call_med->tp_ready != PJ_SUCCESS)
1557 status = call_med->tp_ready;
1558 }
1559
1560 /* OK, we are called by the last media transport finished. */
1561 pj_mutex_unlock(call->med_ch_mutex);
1562 }
1563
1564 if (call->med_ch_mutex) {
1565 pj_mutex_destroy(call->med_ch_mutex);
1566 call->med_ch_mutex = NULL;
1567 }
1568
1569 if (status != PJ_SUCCESS) {
1570 pjsua_media_channel_deinit(call_id);
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001571 goto on_return;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001572 }
1573
1574 /* Tell the media transport of a new offer/answer session */
1575 for (mi=0; mi < call->med_cnt; ++mi) {
1576 pjsua_call_media *call_med = &call->media[mi];
1577
1578 /* Note: tp may be NULL if this media line is disabled */
1579 if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) {
Sauw Ming99cc8ff2011-09-22 04:24:56 +00001580 pj_pool_t *tmp_pool = call->async_call.pool_prov;
1581
1582 if (!tmp_pool) {
1583 tmp_pool = (call->inv? call->inv->pool_prov:
1584 call->async_call.dlg->pool);
1585 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001586
Sauw Minge7dbbc82011-10-24 09:28:13 +00001587 if (call_med->use_custom_med_tp) {
1588 unsigned custom_med_tp_flags = 0;
1589
1590 /* Use custom media transport returned by the application */
1591 call_med->tp =
1592 (*pjsua_var.ua_cfg.cb.on_create_media_transport)
1593 (call_id, mi, call_med->tp,
1594 custom_med_tp_flags);
1595 if (!call_med->tp) {
1596 status =
1597 PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
1598 }
1599 }
1600
1601 if (call_med->tp) {
1602 status = pjmedia_transport_media_create(
1603 call_med->tp, tmp_pool,
1604 0, call->async_call.rem_sdp, mi);
1605 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001606 if (status != PJ_SUCCESS) {
1607 call->med_ch_info.status = status;
1608 call->med_ch_info.med_idx = mi;
1609 call->med_ch_info.state = call_med->tp_st;
1610 call->med_ch_info.sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1611 pjsua_media_channel_deinit(call_id);
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001612 goto on_return;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001613 }
1614
1615 set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
1616 }
1617 }
1618
1619 call->med_ch_info.status = PJ_SUCCESS;
1620
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001621on_return:
Sauw Ming73ecfe82011-09-21 10:20:01 +00001622 if (call->med_ch_cb)
1623 (*call->med_ch_cb)(call->index, &call->med_ch_info);
1624
Benny Prijono0bc99a92011-03-17 04:34:43 +00001625 return status;
1626}
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001627
Benny Prijonod8179652008-01-23 20:39:07 +00001628pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
1629 pjsip_role_e role,
1630 int security_level,
1631 pj_pool_t *tmp_pool,
1632 const pjmedia_sdp_session *rem_sdp,
Sauw Ming73ecfe82011-09-21 10:20:01 +00001633 int *sip_err_code,
1634 pj_bool_t async,
1635 pjsua_med_tp_state_cb cb)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001636{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001637 const pj_str_t STR_AUDIO = { "audio", 5 };
1638 const pj_str_t STR_VIDEO = { "video", 5 };
Benny Prijonod8179652008-01-23 20:39:07 +00001639 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijonod8179652008-01-23 20:39:07 +00001640 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001641 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1642 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001643 unsigned mtotaudcnt = PJ_ARRAY_SIZE(maudidx);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001644 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
1645 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001646 unsigned mtotvidcnt = PJ_ARRAY_SIZE(mvididx);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001647 unsigned mi;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001648 pj_bool_t pending_med_tp = PJ_FALSE;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001649 pj_bool_t reinit = PJ_FALSE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001650 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001651
Benny Prijonod8179652008-01-23 20:39:07 +00001652 PJ_UNUSED_ARG(role);
1653
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001654 /*
1655 * Note: this function may be called when the media already exists
1656 * (e.g. in reinvites, updates, etc).
1657 */
1658
Benny Prijono0bc99a92011-03-17 04:34:43 +00001659 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
1660 return PJ_EBUSY;
1661
Sauw Ming73ecfe82011-09-21 10:20:01 +00001662 if (async) {
1663 pj_pool_t *tmppool = (call->inv? call->inv->pool_prov:
1664 call->async_call.dlg->pool);
1665
1666 status = pj_mutex_create_simple(tmppool, NULL, &call->med_ch_mutex);
1667 if (status != PJ_SUCCESS)
1668 return status;
1669 }
1670
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001671 if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED)
1672 reinit = PJ_TRUE;
1673
1674 PJ_LOG(4,(THIS_FILE, "Call %d: %sinitializing media..",
1675 call_id, (reinit?"re-":"") ));
1676
Benny Prijonob90fd382011-09-18 14:59:56 +00001677 pj_log_push_indent();
1678
Benny Prijono0bc99a92011-03-17 04:34:43 +00001679#if DISABLED_FOR_TICKET_1185
Benny Prijonod8179652008-01-23 20:39:07 +00001680 /* Return error if media transport has not been created yet
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001681 * (e.g. application is starting)
1682 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001683 for (i=0; i<call->med_cnt; ++i) {
1684 if (call->media[i].tp == NULL) {
Benny Prijonob90fd382011-09-18 14:59:56 +00001685 status = PJ_EBUSY;
1686 goto on_error;
Benny Prijonod8179652008-01-23 20:39:07 +00001687 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001688 }
Benny Prijonod8179652008-01-23 20:39:07 +00001689#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +00001690
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001691 /* Get media count for each media type */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001692 if (rem_sdp) {
1693 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001694 maudidx, &maudcnt, &mtotaudcnt);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001695 if (maudcnt==0) {
1696 /* Expecting audio in the offer */
1697 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1698 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001699 status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
1700 goto on_error;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001701 }
1702
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001703#if PJMEDIA_HAS_VIDEO
Benny Prijono0bc99a92011-03-17 04:34:43 +00001704 sort_media(rem_sdp, &STR_VIDEO, acc->cfg.use_srtp,
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001705 mvididx, &mvidcnt, &mtotvidcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001706#else
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001707 mvidcnt = mtotvidcnt = 0;
1708 PJ_UNUSED_ARG(STR_VIDEO);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001709#endif
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001710
1711 /* Update media count only when remote add any media, this media count
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001712 * must never decrease. Also note that we shouldn't apply the media
1713 * count setting (of the call setting) before the SDP negotiation.
Benny Prijonod8179652008-01-23 20:39:07 +00001714 */
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001715 if (call->med_cnt < rem_sdp->media_count)
1716 call->med_cnt = PJ_MIN(rem_sdp->media_count, PJSUA_MAX_CALL_MEDIA);
Benny Prijonod8179652008-01-23 20:39:07 +00001717
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001718 call->rem_offerer = PJ_TRUE;
1719 call->rem_aud_cnt = maudcnt;
1720 call->rem_vid_cnt = mvidcnt;
1721
Benny Prijono0bc99a92011-03-17 04:34:43 +00001722 } else {
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001723
1724 /* If call already established, calculate media count from current
1725 * local active SDP and call setting. Otherwise, calculate media
1726 * count from the call setting only.
1727 */
1728 if (reinit) {
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001729 const pjmedia_sdp_session *sdp;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001730
1731 status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &sdp);
1732 pj_assert(status == PJ_SUCCESS);
1733
1734 sort_media(sdp, &STR_AUDIO, acc->cfg.use_srtp,
1735 maudidx, &maudcnt, &mtotaudcnt);
1736 pj_assert(maudcnt > 0);
1737
1738 sort_media(sdp, &STR_VIDEO, acc->cfg.use_srtp,
1739 mvididx, &mvidcnt, &mtotvidcnt);
1740
1741 /* Call setting may add or remove media. Adding media is done by
1742 * enabling any disabled/port-zeroed media first, then adding new
1743 * media whenever needed. Removing media is done by disabling
1744 * media with the lowest 'quality'.
1745 */
1746
1747 /* Check if we need to add new audio */
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001748 if (maudcnt < call->opt.aud_cnt &&
1749 mtotaudcnt < call->opt.aud_cnt)
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001750 {
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001751 for (mi = 0; mi < call->opt.aud_cnt - mtotaudcnt; ++mi)
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001752 maudidx[maudcnt++] = (pj_uint8_t)call->med_cnt++;
1753
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001754 mtotaudcnt = call->opt.aud_cnt;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001755 }
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001756 maudcnt = call->opt.aud_cnt;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001757
1758 /* Check if we need to add new video */
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001759 if (mvidcnt < call->opt.vid_cnt &&
1760 mtotvidcnt < call->opt.vid_cnt)
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001761 {
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001762 for (mi = 0; mi < call->opt.vid_cnt - mtotvidcnt; ++mi)
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001763 mvididx[mvidcnt++] = (pj_uint8_t)call->med_cnt++;
1764
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001765 mtotvidcnt = call->opt.vid_cnt;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001766 }
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001767 mvidcnt = call->opt.vid_cnt;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001768
1769 } else {
1770
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001771 maudcnt = mtotaudcnt = call->opt.aud_cnt;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001772 for (mi=0; mi<maudcnt; ++mi) {
1773 maudidx[mi] = (pj_uint8_t)mi;
1774 }
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001775 mvidcnt = mtotvidcnt = call->opt.vid_cnt;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001776 for (mi=0; mi<mvidcnt; ++mi) {
1777 mvididx[mi] = (pj_uint8_t)(maudcnt + mi);
1778 }
1779 call->med_cnt = maudcnt + mvidcnt;
1780
1781 /* Need to publish supported media? */
1782 if (call->opt.flag & PJSUA_CALL_INCLUDE_DISABLED_MEDIA) {
1783 if (mtotaudcnt == 0) {
1784 mtotaudcnt = 1;
1785 maudidx[0] = (pj_uint8_t)call->med_cnt++;
1786 }
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001787#if PJMEDIA_HAS_VIDEO
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001788 if (mtotvidcnt == 0) {
1789 mtotvidcnt = 1;
1790 mvididx[0] = (pj_uint8_t)call->med_cnt++;
1791 }
1792#endif
1793 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001794 }
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001795
1796 call->rem_offerer = PJ_FALSE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001797 }
1798
Benny Prijono0bc99a92011-03-17 04:34:43 +00001799 if (call->med_cnt == 0) {
1800 /* Expecting at least one media */
Benny Prijonoab8dba92008-06-27 21:59:15 +00001801 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001802 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001803 status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
1804 goto on_error;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001805 }
1806
Sauw Ming73ecfe82011-09-21 10:20:01 +00001807 if (async) {
1808 call->med_ch_cb = cb;
Benny Prijono7eff5ef2011-10-26 06:53:30 +00001809 }
1810
1811 if (rem_sdp) {
1812 call->async_call.rem_sdp =
1813 pjmedia_sdp_session_clone(call->inv->pool_prov, rem_sdp);
1814 } else {
1815 call->async_call.rem_sdp = NULL;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001816 }
1817
Sauw Ming99cc8ff2011-09-22 04:24:56 +00001818 call->async_call.pool_prov = tmp_pool;
1819
Benny Prijono0bc99a92011-03-17 04:34:43 +00001820 /* Initialize each media line */
1821 for (mi=0; mi < call->med_cnt; ++mi) {
1822 pjsua_call_media *call_med = &call->media[mi];
1823 pj_bool_t enabled = PJ_FALSE;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001824 pjmedia_type media_type = PJMEDIA_TYPE_UNKNOWN;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001825
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001826 if (pj_memchr(maudidx, mi, mtotaudcnt * sizeof(maudidx[0]))) {
1827 media_type = PJMEDIA_TYPE_AUDIO;
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001828 if (call->opt.aud_cnt &&
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001829 pj_memchr(maudidx, mi, maudcnt * sizeof(maudidx[0])))
1830 {
1831 enabled = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001832 }
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001833 } else if (pj_memchr(mvididx, mi, mtotvidcnt * sizeof(mvididx[0]))) {
1834 media_type = PJMEDIA_TYPE_VIDEO;
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001835 if (call->opt.vid_cnt &&
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001836 pj_memchr(mvididx, mi, mvidcnt * sizeof(mvididx[0])))
1837 {
1838 enabled = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001839 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001840 }
1841
1842 if (enabled) {
1843 status = pjsua_call_media_init(call_med, media_type,
1844 &acc->cfg.rtp_cfg,
Sauw Ming73ecfe82011-09-21 10:20:01 +00001845 security_level, sip_err_code,
1846 async,
Sauw Ming3a55bb92011-10-06 06:49:09 +00001847 (async? &media_channel_init_cb:
1848 NULL));
Sauw Ming73ecfe82011-09-21 10:20:01 +00001849 if (status == PJ_EPENDING) {
1850 pending_med_tp = PJ_TRUE;
1851 } else if (status != PJ_SUCCESS) {
1852 if (pending_med_tp) {
1853 /* Save failure information. */
1854 call_med->tp_ready = status;
1855 pj_bzero(&call->med_ch_info, sizeof(call->med_ch_info));
1856 call->med_ch_info.status = status;
1857 call->med_ch_info.state = call_med->tp_st;
1858 call->med_ch_info.med_idx = call_med->idx;
1859 if (sip_err_code)
1860 call->med_ch_info.sip_err_code = *sip_err_code;
1861
1862 /* We will return failure in the callback later. */
1863 return PJ_EPENDING;
1864 }
1865
1866 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001867 goto on_error;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001868 }
1869 } else {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001870 /* By convention, the media is disabled if transport is NULL
1871 * or transport state is PJSUA_MED_TP_DISABLED.
1872 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001873 if (call_med->tp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001874 // Don't close transport here, as SDP negotiation has not been
1875 // done and stream may be still active.
1876 //pjmedia_transport_close(call_med->tp);
1877 //call_med->tp = NULL;
1878 pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT ||
1879 call_med->tp_st == PJSUA_MED_TP_RUNNING);
Sauw Ming73ecfe82011-09-21 10:20:01 +00001880 set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001881 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001882
1883 /* Put media type just for info */
1884 call_med->type = media_type;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001885 }
Benny Prijono224b4e22008-06-19 14:10:28 +00001886 }
1887
Benny Prijono0bc99a92011-03-17 04:34:43 +00001888 call->audio_idx = maudidx[0];
1889
1890 PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d",
1891 call->audio_idx, call->index));
1892
Sauw Ming73ecfe82011-09-21 10:20:01 +00001893 if (pending_med_tp) {
Sauw Ming99cc8ff2011-09-22 04:24:56 +00001894 /* We shouldn't use temporary pool anymore. */
1895 call->async_call.pool_prov = NULL;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001896 /* We have a pending media transport initialization. */
1897 pj_log_pop_indent();
1898 return PJ_EPENDING;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001899 }
1900
Sauw Ming73ecfe82011-09-21 10:20:01 +00001901 /* Media transport initialization completed immediately, so
1902 * we don't need to call the callback.
1903 */
1904 call->med_ch_cb = NULL;
1905
1906 status = media_channel_init_cb(call_id, NULL);
1907 if (status != PJ_SUCCESS && sip_err_code)
1908 *sip_err_code = call->med_ch_info.sip_err_code;
1909
Benny Prijonob90fd382011-09-18 14:59:56 +00001910 pj_log_pop_indent();
Sauw Ming73ecfe82011-09-21 10:20:01 +00001911 return status;
Benny Prijonob90fd382011-09-18 14:59:56 +00001912
1913on_error:
Sauw Ming73ecfe82011-09-21 10:20:01 +00001914 if (call->med_ch_mutex) {
1915 pj_mutex_destroy(call->med_ch_mutex);
1916 call->med_ch_mutex = NULL;
1917 }
1918
Benny Prijonob90fd382011-09-18 14:59:56 +00001919 pj_log_pop_indent();
1920 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001921}
1922
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001923
1924/* Create SDP based on the current media channel. Note that, this function
1925 * will not modify the media channel, so when receiving new offer or
1926 * updating media count (via call setting), media channel must be reinit'd
1927 * (using pjsua_media_channel_init()) first before calling this function.
1928 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001929pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1930 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001931 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001932 pjmedia_sdp_session **p_sdp,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001933 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001934{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001935 enum { MAX_MEDIA = PJSUA_MAX_CALL_MEDIA };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001936 pjmedia_sdp_session *sdp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001937 pj_sockaddr origin;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001938 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001939 pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001940 unsigned mi;
Nanang Izzuddin2f296352012-01-27 09:12:59 +00001941 unsigned tot_bandw_tias = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001942 pj_status_t status;
1943
Benny Prijono0bc99a92011-03-17 04:34:43 +00001944 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
Benny Prijono55e82352007-05-10 20:49:08 +00001945 return PJ_EBUSY;
Benny Prijono55e82352007-05-10 20:49:08 +00001946
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001947#if 0
1948 // This function should not really change the media channel.
Benny Prijono0bc99a92011-03-17 04:34:43 +00001949 if (rem_sdp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001950 /* If this is a re-offer, let's re-initialize media as remote may
1951 * add or remove media
1952 */
1953 if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) {
1954 status = pjsua_media_channel_init(call_id, PJSIP_ROLE_UAS,
1955 call->secure_level, pool,
Sauw Ming73ecfe82011-09-21 10:20:01 +00001956 rem_sdp, sip_err_code,
1957 PJ_FALSE, NULL);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001958 if (status != PJ_SUCCESS)
1959 return status;
Benny Prijono0324ba52010-12-02 10:41:46 +00001960 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001961 } else {
1962 /* Audio is first in our offer, by convention */
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001963 // The audio_idx should not be changed here, as this function may be
1964 // called in generating re-offer and the current active audio index
1965 // can be anywhere.
1966 //call->audio_idx = 0;
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001967 }
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001968#endif
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001969
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001970#if 0
1971 // Since r3512, old-style hold should have got transport, created by
1972 // pjsua_media_channel_init() in initial offer/answer or remote reoffer.
Benny Prijono224b4e22008-06-19 14:10:28 +00001973 /* Create media if it's not created. This could happen when call is
Benny Prijono0bc99a92011-03-17 04:34:43 +00001974 * currently on-hold (with the old style hold)
Benny Prijono224b4e22008-06-19 14:10:28 +00001975 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001976 if (call->media[call->audio_idx].tp == NULL) {
Benny Prijono224b4e22008-06-19 14:10:28 +00001977 pjsip_role_e role;
1978 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1979 status = pjsua_media_channel_init(call_id, role, call->secure_level,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001980 pool, rem_sdp, sip_err_code);
Benny Prijono224b4e22008-06-19 14:10:28 +00001981 if (status != PJ_SUCCESS)
1982 return status;
1983 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001984#endif
Benny Prijono224b4e22008-06-19 14:10:28 +00001985
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001986 /* Get SDP negotiator state */
1987 if (call->inv && call->inv->neg)
1988 sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg);
1989
Benny Prijono0bc99a92011-03-17 04:34:43 +00001990 /* Get one address to use in the origin field */
1991 pj_bzero(&origin, sizeof(origin));
1992 for (mi=0; mi<call->med_cnt; ++mi) {
1993 pjmedia_transport_info tpinfo;
Benny Prijono617c5bc2007-04-02 19:51:21 +00001994
Benny Prijono0bc99a92011-03-17 04:34:43 +00001995 if (call->media[mi].tp == NULL)
1996 continue;
1997
1998 pjmedia_transport_info_init(&tpinfo);
1999 pjmedia_transport_get_info(call->media[mi].tp, &tpinfo);
2000 pj_sockaddr_cp(&origin, &tpinfo.sock_info.rtp_addr_name);
2001 break;
Benny Prijono25b2ea12008-01-24 19:20:54 +00002002 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002003
Benny Prijono0bc99a92011-03-17 04:34:43 +00002004 /* Create the base (blank) SDP */
2005 status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, pool, NULL,
2006 &origin, &sdp);
2007 if (status != PJ_SUCCESS)
2008 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00002009
Benny Prijono0bc99a92011-03-17 04:34:43 +00002010 /* Process each media line */
2011 for (mi=0; mi<call->med_cnt; ++mi) {
2012 pjsua_call_media *call_med = &call->media[mi];
2013 pjmedia_sdp_media *m = NULL;
2014 pjmedia_transport_info tpinfo;
Nanang Izzuddin2f296352012-01-27 09:12:59 +00002015 unsigned i;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002016
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002017 if (rem_sdp && mi >= rem_sdp->media_count) {
2018 /* Remote might have removed some media lines. */
2019 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002020 }
2021
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002022 if (call_med->tp == NULL || call_med->tp_st == PJSUA_MED_TP_DISABLED)
2023 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002024 /*
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002025 * This media is disabled. Just create a valid SDP with zero
Benny Prijono0bc99a92011-03-17 04:34:43 +00002026 * port.
2027 */
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002028 if (rem_sdp) {
2029 /* Just clone the remote media and deactivate it */
2030 m = pjmedia_sdp_media_clone_deactivate(pool,
2031 rem_sdp->media[mi]);
2032 } else {
2033 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
2034 m->desc.transport = pj_str("RTP/AVP");
2035 m->desc.fmt_count = 1;
2036 m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
2037 m->conn->net_type = pj_str("IN");
2038 m->conn->addr_type = pj_str("IP4");
2039 m->conn->addr = pj_str("127.0.0.1");
Benny Prijonoa310bd22008-06-27 21:19:44 +00002040
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002041 switch (call_med->type) {
2042 case PJMEDIA_TYPE_AUDIO:
2043 m->desc.media = pj_str("audio");
2044 m->desc.fmt[0] = pj_str("0");
2045 break;
2046 case PJMEDIA_TYPE_VIDEO:
2047 m->desc.media = pj_str("video");
2048 m->desc.fmt[0] = pj_str("31");
2049 break;
2050 default:
Nanang Izzuddincfa312c2011-10-26 16:57:05 +00002051 /* This must be us generating re-offer, and some unknown
2052 * media may exist, so just clone from active local SDP
2053 * (and it should have been deactivated already).
2054 */
2055 pj_assert(call->inv && call->inv->neg &&
2056 sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE);
2057 {
2058 const pjmedia_sdp_session *s_;
2059 pjmedia_sdp_neg_get_active_local(call->inv->neg, &s_);
2060
2061 pj_assert(mi < s_->media_count);
2062 m = pjmedia_sdp_media_clone(pool, s_->media[mi]);
2063 m->desc.port = 0;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002064 }
Nanang Izzuddincfa312c2011-10-26 16:57:05 +00002065 break;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002066 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00002067 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00002068
2069 sdp->media[sdp->media_count++] = m;
2070 continue;
Benny Prijonoa310bd22008-06-27 21:19:44 +00002071 }
2072
Benny Prijono0bc99a92011-03-17 04:34:43 +00002073 /* Get transport address info */
2074 pjmedia_transport_info_init(&tpinfo);
2075 pjmedia_transport_get_info(call_med->tp, &tpinfo);
Benny Prijonoa310bd22008-06-27 21:19:44 +00002076
Benny Prijono0bc99a92011-03-17 04:34:43 +00002077 /* Ask pjmedia endpoint to create SDP media line */
2078 switch (call_med->type) {
2079 case PJMEDIA_TYPE_AUDIO:
2080 status = pjmedia_endpt_create_audio_sdp(pjsua_var.med_endpt, pool,
2081 &tpinfo.sock_info, 0, &m);
2082 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00002083#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002084 case PJMEDIA_TYPE_VIDEO:
2085 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
2086 &tpinfo.sock_info, 0, &m);
2087 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00002088#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002089 default:
2090 pj_assert(!"Invalid call_med media type");
2091 return PJ_EBUG;
2092 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00002093
Benny Prijono0bc99a92011-03-17 04:34:43 +00002094 if (status != PJ_SUCCESS)
2095 return status;
2096
2097 sdp->media[sdp->media_count++] = m;
2098
2099 /* Give to transport */
2100 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
2101 sdp, rem_sdp, mi);
2102 if (status != PJ_SUCCESS) {
2103 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
2104 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00002105 }
Nanang Izzuddin91ba2a22011-04-11 19:59:09 +00002106
2107 /* Copy c= line of the first media to session level,
2108 * if there's none.
2109 */
2110 if (sdp->conn == NULL) {
2111 sdp->conn = pjmedia_sdp_conn_clone(pool, m->conn);
Benny Prijonoa310bd22008-06-27 21:19:44 +00002112 }
Nanang Izzuddin2f296352012-01-27 09:12:59 +00002113
2114
2115 /* Find media bandwidth info */
2116 for (i = 0; i < m->bandw_count; ++i) {
2117 const pj_str_t STR_BANDW_MODIFIER_TIAS = { "TIAS", 4 };
2118 if (!pj_stricmp(&m->bandw[i]->modifier, &STR_BANDW_MODIFIER_TIAS))
2119 {
2120 tot_bandw_tias += m->bandw[i]->value;
2121 break;
2122 }
2123 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00002124 }
2125
Benny Prijono6ba8c542007-10-16 01:34:14 +00002126 /* Add NAT info in the SDP */
2127 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
2128 pjmedia_sdp_attr *a;
2129 pj_str_t value;
2130 char nat_info[80];
2131
2132 value.ptr = nat_info;
2133 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
2134 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
2135 "%d", pjsua_var.nat_type);
2136 } else {
2137 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
2138 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
2139 "%d %s",
2140 pjsua_var.nat_type,
2141 type_name);
2142 }
2143
2144 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
2145
2146 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
2147
2148 }
2149
Benny Prijonoc97608e2007-03-23 16:34:20 +00002150
Nanang Izzuddin2f296352012-01-27 09:12:59 +00002151 /* Add bandwidth info in session level using bandwidth modifier "AS". */
2152 if (tot_bandw_tias) {
2153 unsigned bandw;
2154 const pj_str_t STR_BANDW_MODIFIER_AS = { "AS", 2 };
2155 pjmedia_sdp_bandw *b;
2156
2157 /* AS bandwidth = RTP bitrate + RTCP bitrate.
2158 * RTP bitrate = payload bitrate (total TIAS) + overheads (~16kbps).
2159 * RTCP bitrate = est. 5% of RTP bitrate.
2160 * Note that AS bandwidth is in kbps.
2161 */
2162 bandw = tot_bandw_tias + 16000;
2163 bandw += bandw * 5 / 100;
2164 b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw);
2165 b->modifier = STR_BANDW_MODIFIER_AS;
2166 b->value = bandw / 1000;
2167 sdp->bandw[sdp->bandw_count++] = b;
2168 }
2169
2170
Benny Prijono0bc99a92011-03-17 04:34:43 +00002171#if DISABLED_FOR_TICKET_1185 && defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002172 /* Check if SRTP is in optional mode and configured to use duplicated
2173 * media, i.e: secured and unsecured version, in the SDP offer.
2174 */
2175 if (!rem_sdp &&
2176 pjsua_var.acc[call->acc_id].cfg.use_srtp == PJMEDIA_SRTP_OPTIONAL &&
2177 pjsua_var.acc[call->acc_id].cfg.srtp_optional_dup_offer)
2178 {
2179 unsigned i;
2180
2181 for (i = 0; i < sdp->media_count; ++i) {
2182 pjmedia_sdp_media *m = sdp->media[i];
2183
Benny Prijono0bc99a92011-03-17 04:34:43 +00002184 /* Check if this media is unsecured but has SDP "crypto"
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002185 * attribute.
2186 */
2187 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") == 0 &&
2188 pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL)
2189 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002190 if (i == (unsigned)call->audio_idx &&
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002191 sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE)
2192 {
2193 /* This is a session update, and peer has chosen the
2194 * unsecured version, so let's make this unsecured too.
2195 */
2196 pjmedia_sdp_media_remove_all_attr(m, "crypto");
2197 } else {
2198 /* This is new offer, duplicate media so we'll have
2199 * secured (with "RTP/SAVP" transport) and and unsecured
2200 * versions.
2201 */
2202 pjmedia_sdp_media *new_m;
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002203
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002204 /* Duplicate this media and apply secured transport */
2205 new_m = pjmedia_sdp_media_clone(pool, m);
2206 pj_strdup2(pool, &new_m->desc.transport, "RTP/SAVP");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002207
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002208 /* Remove the "crypto" attribute in the unsecured media */
2209 pjmedia_sdp_media_remove_all_attr(m, "crypto");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002210
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002211 /* Insert the new media before the unsecured media */
2212 if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002213 pj_array_insert(sdp->media, sizeof(new_m),
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002214 sdp->media_count, i, &new_m);
2215 ++sdp->media_count;
2216 ++i;
2217 }
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002218 }
2219 }
2220 }
2221 }
2222#endif
2223
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002224 call->rem_offerer = (rem_sdp != NULL);
2225
Benny Prijonoc97608e2007-03-23 16:34:20 +00002226 *p_sdp = sdp;
2227 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002228}
2229
2230
2231static void stop_media_session(pjsua_call_id call_id)
2232{
2233 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002234 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002235
Benny Prijonob90fd382011-09-18 14:59:56 +00002236 pj_log_push_indent();
2237
Benny Prijono0bc99a92011-03-17 04:34:43 +00002238 for (mi=0; mi<call->med_cnt; ++mi) {
2239 pjsua_call_media *call_med = &call->media[mi];
2240
2241 if (call_med->type == PJMEDIA_TYPE_AUDIO) {
2242 pjmedia_stream *strm = call_med->strm.a.stream;
2243 pjmedia_rtcp_stat stat;
2244
2245 if (strm) {
2246 if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) {
2247 if (pjsua_var.mconf) {
2248 pjsua_conf_remove_port(call_med->strm.a.conf_slot);
2249 }
2250 call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
2251 }
2252
2253 if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
2254 (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS))
2255 {
2256 /* Save RTP timestamp & sequence, so when media session is
2257 * restarted, those values will be restored as the initial
2258 * RTP timestamp & sequence of the new media session. So in
2259 * the same call session, RTP timestamp and sequence are
2260 * guaranteed to be contigue.
2261 */
2262 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
2263 call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
2264 call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
2265 }
2266
2267 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
2268 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, strm, mi);
2269 }
2270
2271 pjmedia_stream_destroy(strm);
2272 call_med->strm.a.stream = NULL;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002273 }
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00002274 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002275
2276#if PJMEDIA_HAS_VIDEO
2277 else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
Nanang Izzuddin62053a62011-07-12 11:08:32 +00002278 stop_video_stream(call_med);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002279 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002280#endif
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002281
2282 PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed",
2283 call_id, mi));
Sauw Minge7dbbc82011-10-24 09:28:13 +00002284 call_med->prev_state = call_med->state;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002285 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002286 }
Benny Prijonob90fd382011-09-18 14:59:56 +00002287
2288 pj_log_pop_indent();
Benny Prijonoc97608e2007-03-23 16:34:20 +00002289}
2290
2291pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
2292{
2293 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002294 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002295
Sauw Ming903154f2011-10-03 08:22:48 +00002296 PJSUA_LOCK();
Sauw Mingec765352011-10-03 02:04:36 +00002297 for (mi=0; mi<call->med_cnt; ++mi) {
2298 pjsua_call_media *call_med = &call->media[mi];
2299
Sauw Ming903154f2011-10-03 08:22:48 +00002300 if (call_med->tp_st == PJSUA_MED_TP_CREATING) {
2301 /* We will do the deinitialization after media transport
2302 * creation is completed.
2303 */
2304 call->async_call.med_ch_deinit = PJ_TRUE;
2305 PJSUA_UNLOCK();
2306 return PJ_SUCCESS;
2307 }
Sauw Mingec765352011-10-03 02:04:36 +00002308 }
Sauw Ming903154f2011-10-03 08:22:48 +00002309 PJSUA_UNLOCK();
Sauw Mingec765352011-10-03 02:04:36 +00002310
Benny Prijonob90fd382011-09-18 14:59:56 +00002311 PJ_LOG(4,(THIS_FILE, "Call %d: deinitializing media..", call_id));
2312 pj_log_push_indent();
2313
Sauw Minge7dbbc82011-10-24 09:28:13 +00002314 for (mi=0; mi<call->med_cnt; ++mi) {
2315 pjsua_call_media *call_med = &call->media[mi];
2316
2317 if (call_med->type == PJMEDIA_TYPE_AUDIO && call_med->strm.a.stream)
2318 pjmedia_stream_send_rtcp_bye(call_med->strm.a.stream);
2319 }
2320
Benny Prijonoc97608e2007-03-23 16:34:20 +00002321 stop_media_session(call_id);
2322
Benny Prijono0bc99a92011-03-17 04:34:43 +00002323 for (mi=0; mi<call->med_cnt; ++mi) {
2324 pjsua_call_media *call_med = &call->media[mi];
Benny Prijonoc97608e2007-03-23 16:34:20 +00002325
Sauw Minge7dbbc82011-10-24 09:28:13 +00002326 if (call_med->tp_st > PJSUA_MED_TP_IDLE) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002327 pjmedia_transport_media_stop(call_med->tp);
Sauw Ming73ecfe82011-09-21 10:20:01 +00002328 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002329 }
2330
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002331 //if (call_med->tp_orig && call_med->tp &&
2332 // call_med->tp != call_med->tp_orig)
2333 //{
2334 // pjmedia_transport_close(call_med->tp);
2335 // call_med->tp = call_med->tp_orig;
2336 //}
2337 if (call_med->tp) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002338 pjmedia_transport_close(call_med->tp);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002339 call_med->tp = call_med->tp_orig = NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002340 }
Sauw Minge7dbbc82011-10-24 09:28:13 +00002341 call_med->tp_orig = NULL;
Benny Prijonod8179652008-01-23 20:39:07 +00002342 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00002343
2344 check_snd_dev_idle();
Benny Prijonob90fd382011-09-18 14:59:56 +00002345 pj_log_pop_indent();
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00002346
Benny Prijonoc97608e2007-03-23 16:34:20 +00002347 return PJ_SUCCESS;
2348}
2349
2350
2351/*
2352 * DTMF callback from the stream.
2353 */
2354static void dtmf_callback(pjmedia_stream *strm, void *user_data,
2355 int digit)
2356{
2357 PJ_UNUSED_ARG(strm);
2358
Benny Prijonob90fd382011-09-18 14:59:56 +00002359 pj_log_push_indent();
2360
Benny Prijono0c068262008-02-14 14:38:52 +00002361 /* For discussions about call mutex protection related to this
2362 * callback, please see ticket #460:
2363 * http://trac.pjsip.org/repos/ticket/460#comment:4
2364 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00002365 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
2366 pjsua_call_id call_id;
2367
Benny Prijonod8179652008-01-23 20:39:07 +00002368 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002369 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
2370 }
Benny Prijonob90fd382011-09-18 14:59:56 +00002371
2372 pj_log_pop_indent();
Benny Prijonoc97608e2007-03-23 16:34:20 +00002373}
2374
2375
Benny Prijono0bc99a92011-03-17 04:34:43 +00002376static pj_status_t audio_channel_update(pjsua_call_media *call_med,
2377 pj_pool_t *tmp_pool,
2378 const pjmedia_sdp_session *local_sdp,
2379 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00002380{
Benny Prijono0bc99a92011-03-17 04:34:43 +00002381 pjsua_call *call = call_med->call;
2382 pjmedia_stream_info the_si, *si = &the_si;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002383 pjmedia_port *media_port;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002384 unsigned strm_idx = call_med->idx;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002385 pj_status_t status;
Benny Prijonob90fd382011-09-18 14:59:56 +00002386
2387 PJ_LOG(4,(THIS_FILE,"Audio channel update.."));
2388 pj_log_push_indent();
Benny Prijono0bc99a92011-03-17 04:34:43 +00002389
2390 status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
2391 local_sdp, remote_sdp, strm_idx);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002392 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002393 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002394
Sauw Minge7dbbc82011-10-24 09:28:13 +00002395 si->rtcp_sdes_bye_disabled = PJ_TRUE;
2396
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002397 /* Check if no media is active */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002398 if (si->dir == PJMEDIA_DIR_NONE) {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002399 /* Call media state */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002400 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002401
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002402 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002403 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijono68f9e4f2008-03-21 08:56:02 +00002404
Benny Prijonoc97608e2007-03-23 16:34:20 +00002405 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002406 pjmedia_transport_info tp_info;
2407
Benny Prijono224b4e22008-06-19 14:10:28 +00002408 /* Start/restart media transport */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002409 status = pjmedia_transport_media_start(call_med->tp,
2410 tmp_pool, local_sdp,
2411 remote_sdp, strm_idx);
Benny Prijonod8179652008-01-23 20:39:07 +00002412 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002413 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002414
Sauw Ming73ecfe82011-09-21 10:20:01 +00002415 set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING);
Benny Prijono224b4e22008-06-19 14:10:28 +00002416
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002417 /* Get remote SRTP usage policy */
2418 pjmedia_transport_info_init(&tp_info);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002419 pjmedia_transport_get_info(call_med->tp, &tp_info);
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002420 if (tp_info.specific_info_cnt > 0) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +00002421 unsigned i;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002422 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
2423 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
2424 {
2425 pjmedia_srtp_info *srtp_info =
2426 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
2427
Benny Prijono0bc99a92011-03-17 04:34:43 +00002428 call_med->rem_srtp_use = srtp_info->peer_use;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002429 break;
2430 }
2431 }
2432 }
2433
Benny Prijonoc97608e2007-03-23 16:34:20 +00002434 /* Override ptime, if this option is specified. */
2435 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00002436 si->param->setting.frm_per_pkt = (pj_uint8_t)
2437 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
2438 if (si->param->setting.frm_per_pkt == 0)
2439 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002440 }
2441
2442 /* Disable VAD, if this option is specified. */
2443 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00002444 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002445 }
2446
2447
2448 /* Optionally, application may modify other stream settings here
2449 * (such as jitter buffer parameters, codec ptime, etc.)
2450 */
Benny Prijono91e567e2007-12-28 08:51:58 +00002451 si->jb_init = pjsua_var.media_cfg.jb_init;
2452 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
2453 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
2454 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002455
Benny Prijono8147f402007-11-21 14:50:07 +00002456 /* Set SSRC */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002457 si->ssrc = call_med->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00002458
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002459 /* Set RTP timestamp & sequence, normally these value are intialized
2460 * automatically when stream session created, but for some cases (e.g:
2461 * call reinvite, call update) timestamp and sequence need to be kept
2462 * contigue.
2463 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002464 si->rtp_ts = call_med->rtp_tx_ts;
2465 si->rtp_seq = call_med->rtp_tx_seq;
2466 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002467
Nanang Izzuddin5e39a2b2010-09-20 06:13:02 +00002468#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
2469 /* Enable/disable stream keep-alive and NAT hole punch. */
2470 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
2471#endif
2472
Benny Prijonoc97608e2007-03-23 16:34:20 +00002473 /* Create session based on session info. */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002474 status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,
2475 call_med->tp, NULL,
2476 &call_med->strm.a.stream);
2477 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002478 goto on_return;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002479 }
2480
2481 /* Start stream */
2482 status = pjmedia_stream_start(call_med->strm.a.stream);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002483 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002484 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002485 }
2486
Sauw Minge7dbbc82011-10-24 09:28:13 +00002487 if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE)
2488 pjmedia_stream_send_rtcp_sdes(call_med->strm.a.stream);
2489
Benny Prijonoc97608e2007-03-23 16:34:20 +00002490 /* If DTMF callback is installed by application, install our
2491 * callback to the session.
2492 */
2493 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002494 pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
2495 &dtmf_callback,
2496 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00002497 }
2498
2499 /* Get the port interface of the first stream in the session.
2500 * We need the port interface to add to the conference bridge.
2501 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002502 pjmedia_stream_get_port(call_med->strm.a.stream, &media_port);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002503
Benny Prijonofc13bf62008-02-20 08:56:15 +00002504 /* Notify application about stream creation.
2505 * Note: application may modify media_port to point to different
2506 * media port
2507 */
2508 if (pjsua_var.ua_cfg.cb.on_stream_created) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002509 pjsua_var.ua_cfg.cb.on_stream_created(call->index,
2510 call_med->strm.a.stream,
2511 strm_idx, &media_port);
Benny Prijonofc13bf62008-02-20 08:56:15 +00002512 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002513
2514 /*
2515 * Add the call to conference bridge.
2516 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002517 {
2518 char tmp[PJSIP_MAX_URL_SIZE];
2519 pj_str_t port_name;
2520
2521 port_name.ptr = tmp;
2522 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
2523 call->inv->dlg->remote.info->uri,
2524 tmp, sizeof(tmp));
2525 if (port_name.slen < 1) {
2526 port_name = pj_str("call");
2527 }
Benny Prijono40d62b62009-08-12 17:53:47 +00002528 status = pjmedia_conf_add_port( pjsua_var.mconf,
2529 call->inv->pool_prov,
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002530 media_port,
2531 &port_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +00002532 (unsigned*)
2533 &call_med->strm.a.conf_slot);
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002534 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002535 goto on_return;
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002536 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002537 }
2538
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002539 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002540 call_med->dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002541
2542 /* Call media state */
2543 if (call->local_hold)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002544 call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
2545 else if (call_med->dir == PJMEDIA_DIR_DECODING)
2546 call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002547 else
Benny Prijono0bc99a92011-03-17 04:34:43 +00002548 call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002549 }
2550
2551 /* Print info. */
2552 {
2553 char info[80];
2554 int info_len = 0;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002555 int len;
2556 const char *dir;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002557
Benny Prijono0bc99a92011-03-17 04:34:43 +00002558 switch (si->dir) {
2559 case PJMEDIA_DIR_NONE:
2560 dir = "inactive";
2561 break;
2562 case PJMEDIA_DIR_ENCODING:
2563 dir = "sendonly";
2564 break;
2565 case PJMEDIA_DIR_DECODING:
2566 dir = "recvonly";
2567 break;
2568 case PJMEDIA_DIR_ENCODING_DECODING:
2569 dir = "sendrecv";
2570 break;
2571 default:
2572 dir = "unknown";
2573 break;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002574 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00002575 len = pj_ansi_sprintf( info+info_len,
2576 ", stream #%d: %.*s (%s)", strm_idx,
2577 (int)si->fmt.encoding_name.slen,
2578 si->fmt.encoding_name.ptr,
2579 dir);
2580 if (len > 0)
2581 info_len += len;
Benny Prijonob90fd382011-09-18 14:59:56 +00002582 PJ_LOG(4,(THIS_FILE,"Audio updated%s", info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00002583 }
2584
Benny Prijonob90fd382011-09-18 14:59:56 +00002585on_return:
2586 pj_log_pop_indent();
2587 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002588}
2589
Benny Prijono0bc99a92011-03-17 04:34:43 +00002590pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
2591 const pjmedia_sdp_session *local_sdp,
2592 const pjmedia_sdp_session *remote_sdp)
2593{
2594 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002595 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002596 pj_pool_t *tmp_pool = call->inv->pool_prov;
2597 unsigned mi;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002598 pj_bool_t got_media = PJ_FALSE;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002599 pj_status_t status = PJ_SUCCESS;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002600
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002601 const pj_str_t STR_AUDIO = { "audio", 5 };
2602 const pj_str_t STR_VIDEO = { "video", 5 };
2603 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
2604 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002605 unsigned mtotaudcnt = PJ_ARRAY_SIZE(maudidx);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002606 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
2607 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002608 unsigned mtotvidcnt = PJ_ARRAY_SIZE(mvididx);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002609 pj_bool_t need_renego_sdp = PJ_FALSE;
2610
Benny Prijono0bc99a92011-03-17 04:34:43 +00002611 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
2612 return PJ_EBUSY;
2613
Benny Prijonob90fd382011-09-18 14:59:56 +00002614 PJ_LOG(4,(THIS_FILE, "Call %d: updating media..", call_id));
2615 pj_log_push_indent();
2616
Benny Prijono0bc99a92011-03-17 04:34:43 +00002617 /* Destroy existing media session, if any. */
2618 stop_media_session(call->index);
2619
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002620 /* Call media count must be at least equal to SDP media. Note that
2621 * it may not be equal when remote removed any SDP media line.
2622 */
2623 pj_assert(call->med_cnt >= local_sdp->media_count);
2624
Benny Prijono0bc99a92011-03-17 04:34:43 +00002625 /* Reset audio_idx first */
2626 call->audio_idx = -1;
2627
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002628 /* Sort audio/video based on "quality" */
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002629 sort_media(local_sdp, &STR_AUDIO, acc->cfg.use_srtp,
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002630 maudidx, &maudcnt, &mtotaudcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002631#if PJMEDIA_HAS_VIDEO
2632 sort_media(local_sdp, &STR_VIDEO, acc->cfg.use_srtp,
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002633 mvididx, &mvidcnt, &mtotvidcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002634#else
2635 PJ_UNUSED_ARG(STR_VIDEO);
Nanang Izzuddin3d7385c2011-12-01 10:02:54 +00002636 mvidcnt = mtotvidcnt = 0;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002637#endif
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002638
2639 /* Applying media count limitation. Note that in generating SDP answer,
2640 * no media count limitation applied, as we didn't know yet which media
2641 * would pass the SDP negotiation.
2642 */
Benny Prijonoae86e6a2011-12-27 12:47:52 +00002643 if (maudcnt > call->opt.aud_cnt || mvidcnt > call->opt.vid_cnt)
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002644 {
2645 pjmedia_sdp_session *local_sdp2;
2646
Benny Prijonoae86e6a2011-12-27 12:47:52 +00002647 maudcnt = PJ_MIN(maudcnt, call->opt.aud_cnt);
2648 mvidcnt = PJ_MIN(mvidcnt, call->opt.vid_cnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002649 local_sdp2 = pjmedia_sdp_session_clone(tmp_pool, local_sdp);
2650
2651 for (mi=0; mi < local_sdp2->media_count; ++mi) {
2652 pjmedia_sdp_media *m = local_sdp2->media[mi];
2653
2654 if (m->desc.port == 0 ||
2655 pj_memchr(maudidx, mi, maudcnt*sizeof(maudidx[0])) ||
2656 pj_memchr(mvididx, mi, mvidcnt*sizeof(mvididx[0])))
2657 {
2658 continue;
2659 }
2660
2661 /* Deactivate this media */
2662 pjmedia_sdp_media_deactivate(tmp_pool, m);
2663 }
2664
2665 local_sdp = local_sdp2;
2666 need_renego_sdp = PJ_TRUE;
2667 }
2668
Benny Prijono0bc99a92011-03-17 04:34:43 +00002669 /* Process each media stream */
2670 for (mi=0; mi < call->med_cnt; ++mi) {
2671 pjsua_call_media *call_med = &call->media[mi];
2672
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002673 if (mi >= local_sdp->media_count ||
2674 mi >= remote_sdp->media_count)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002675 {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002676 /* This may happen when remote removed any SDP media lines in
2677 * its re-offer.
2678 */
2679 continue;
2680#if 0
Benny Prijono0bc99a92011-03-17 04:34:43 +00002681 /* Something is wrong */
2682 PJ_LOG(1,(THIS_FILE, "Error updating media for call %d: "
2683 "invalid media index %d in SDP", call_id, mi));
Benny Prijonob90fd382011-09-18 14:59:56 +00002684 status = PJMEDIA_SDP_EINSDP;
2685 goto on_error;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002686#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002687 }
2688
2689 switch (call_med->type) {
2690 case PJMEDIA_TYPE_AUDIO:
2691 status = audio_channel_update(call_med, tmp_pool,
2692 local_sdp, remote_sdp);
2693 if (call->audio_idx==-1 && status==PJ_SUCCESS &&
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002694 call_med->strm.a.stream)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002695 {
2696 call->audio_idx = mi;
2697 }
2698 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00002699#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002700 case PJMEDIA_TYPE_VIDEO:
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002701 status = video_channel_update(call_med, tmp_pool,
2702 local_sdp, remote_sdp);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002703 break;
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002704#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002705 default:
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002706 status = PJMEDIA_EINVALIMEDIATYPE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002707 break;
2708 }
2709
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002710 /* Close the transport of deactivated media, need this here as media
2711 * can be deactivated by the SDP negotiation and the max media count
2712 * (account) setting.
2713 */
2714 if (local_sdp->media[mi]->desc.port==0 && call_med->tp) {
2715 pjmedia_transport_close(call_med->tp);
2716 call_med->tp = call_med->tp_orig = NULL;
Sauw Ming73ecfe82011-09-21 10:20:01 +00002717 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002718 }
2719
Benny Prijono0bc99a92011-03-17 04:34:43 +00002720 if (status != PJ_SUCCESS) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002721 PJ_PERROR(1,(THIS_FILE, status, "Error updating media call%02d:%d",
Benny Prijono0bc99a92011-03-17 04:34:43 +00002722 call_id, mi));
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002723 } else {
2724 got_media = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002725 }
2726 }
2727
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002728 /* Perform SDP re-negotiation if needed. */
2729 if (got_media && need_renego_sdp) {
2730 pjmedia_sdp_neg *neg = call->inv->neg;
2731
2732 /* This should only happen when we are the answerer. */
2733 PJ_ASSERT_RETURN(neg && !pjmedia_sdp_neg_was_answer_remote(neg),
2734 PJMEDIA_SDPNEG_EINSTATE);
2735
2736 status = pjmedia_sdp_neg_set_remote_offer(tmp_pool, neg, remote_sdp);
2737 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002738 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002739
2740 status = pjmedia_sdp_neg_set_local_answer(tmp_pool, neg, local_sdp);
2741 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002742 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002743
2744 status = pjmedia_sdp_neg_negotiate(tmp_pool, neg, 0);
2745 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002746 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002747 }
2748
Benny Prijonob90fd382011-09-18 14:59:56 +00002749 pj_log_pop_indent();
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002750 return (got_media? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA);
Benny Prijonob90fd382011-09-18 14:59:56 +00002751
2752on_error:
2753 pj_log_pop_indent();
2754 return status;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002755}
2756
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002757/*
2758 * Get maxinum number of conference ports.
2759 */
2760PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
2761{
2762 return pjsua_var.media_cfg.max_media_ports;
2763}
2764
2765
2766/*
2767 * Get current number of active ports in the bridge.
2768 */
2769PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
2770{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002771 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002772 unsigned count = PJ_ARRAY_SIZE(ports);
2773 pj_status_t status;
2774
2775 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
2776 if (status != PJ_SUCCESS)
2777 count = 0;
2778
2779 return count;
2780}
2781
2782
2783/*
2784 * Enumerate all conference ports.
2785 */
2786PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
2787 unsigned *count)
2788{
2789 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
2790}
2791
2792
2793/*
2794 * Get information about the specified conference port
2795 */
2796PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
2797 pjsua_conf_port_info *info)
2798{
2799 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00002800 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002801 pj_status_t status;
2802
2803 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
2804 if (status != PJ_SUCCESS)
2805 return status;
2806
Benny Prijonoac623b32006-07-03 15:19:31 +00002807 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002808 info->slot_id = id;
2809 info->name = cinfo.name;
2810 info->clock_rate = cinfo.clock_rate;
2811 info->channel_count = cinfo.channel_count;
2812 info->samples_per_frame = cinfo.samples_per_frame;
2813 info->bits_per_sample = cinfo.bits_per_sample;
2814
2815 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00002816 info->listener_cnt = cinfo.listener_cnt;
2817 for (i=0; i<cinfo.listener_cnt; ++i) {
2818 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002819 }
2820
2821 return PJ_SUCCESS;
2822}
2823
2824
2825/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00002826 * Add arbitrary media port to PJSUA's conference bridge.
2827 */
2828PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
2829 pjmedia_port *port,
2830 pjsua_conf_port_id *p_id)
2831{
2832 pj_status_t status;
2833
2834 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
2835 port, NULL, (unsigned*)p_id);
2836 if (status != PJ_SUCCESS) {
2837 if (p_id)
2838 *p_id = PJSUA_INVALID_ID;
2839 }
2840
2841 return status;
2842}
2843
2844
2845/*
2846 * Remove arbitrary slot from the conference bridge.
2847 */
2848PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
2849{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002850 pj_status_t status;
2851
2852 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
2853 check_snd_dev_idle();
2854
2855 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00002856}
2857
2858
2859/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002860 * Establish unidirectional media flow from souce to sink.
2861 */
2862PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
2863 pjsua_conf_port_id sink)
2864{
Benny Prijonob90fd382011-09-18 14:59:56 +00002865 pj_status_t status = PJ_SUCCESS;
2866
2867 PJ_LOG(4,(THIS_FILE, "%s connect: %d --> %d",
2868 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
2869 source, sink));
2870 pj_log_push_indent();
2871
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002872 /* If sound device idle timer is active, cancel it first. */
Benny Prijono0f711b42009-05-06 19:08:43 +00002873 PJSUA_LOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002874 if (pjsua_var.snd_idle_timer.id) {
2875 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
2876 pjsua_var.snd_idle_timer.id = PJ_FALSE;
2877 }
Benny Prijono0f711b42009-05-06 19:08:43 +00002878 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002879
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002880
Benny Prijonof798e502009-03-09 13:08:16 +00002881 /* For audio switchboard (i.e. APS-Direct):
2882 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002883 * (format, clock rate, channel count) must match to peer's.
2884 * Note that sound device can be reopened only if it doesn't have
2885 * any connection.
2886 */
Benny Prijonof798e502009-03-09 13:08:16 +00002887 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002888 pjmedia_conf_port_info port0_info;
2889 pjmedia_conf_port_info peer_info;
2890 unsigned peer_id;
2891 pj_bool_t need_reopen = PJ_FALSE;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002892
2893 peer_id = (source!=0)? source : sink;
2894 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
2895 &peer_info);
2896 pj_assert(status == PJ_SUCCESS);
2897
2898 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
2899 pj_assert(status == PJ_SUCCESS);
2900
2901 /* Check if sound device is instantiated. */
2902 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2903 !pjsua_var.no_snd);
2904
2905 /* Check if sound device need to reopen because it needs to modify
2906 * settings to match its peer. Sound device must be idle in this case
2907 * though.
2908 */
2909 if (!need_reopen &&
2910 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
2911 {
2912 need_reopen = (peer_info.format.id != port0_info.format.id ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002913 peer_info.format.det.aud.avg_bps !=
2914 port0_info.format.det.aud.avg_bps ||
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002915 peer_info.clock_rate != port0_info.clock_rate ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002916 peer_info.channel_count!=port0_info.channel_count);
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002917 }
2918
2919 if (need_reopen) {
Benny Prijonod65f78c2009-06-03 18:59:37 +00002920 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
Sauw Ming98766c72011-03-11 06:57:24 +00002921 pjmedia_snd_port_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002922
Benny Prijonod65f78c2009-06-03 18:59:37 +00002923 /* Create parameter based on peer info */
Sauw Ming98766c72011-03-11 06:57:24 +00002924 status = create_aud_param(&param.base, pjsua_var.cap_dev,
Benny Prijonod65f78c2009-06-03 18:59:37 +00002925 pjsua_var.play_dev,
2926 peer_info.clock_rate,
2927 peer_info.channel_count,
2928 peer_info.samples_per_frame,
2929 peer_info.bits_per_sample);
2930 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002931 pjsua_perror(THIS_FILE, "Error opening sound device",
2932 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002933 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002934 }
Benny Prijonof798e502009-03-09 13:08:16 +00002935
Benny Prijonod65f78c2009-06-03 18:59:37 +00002936 /* And peer format */
2937 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
Sauw Ming98766c72011-03-11 06:57:24 +00002938 param.base.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
2939 param.base.ext_fmt = peer_info.format;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002940 }
Benny Prijono10454dc2009-02-21 14:21:59 +00002941
Sauw Ming98766c72011-03-11 06:57:24 +00002942 param.options = 0;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002943 status = open_snd_dev(&param);
2944 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002945 pjsua_perror(THIS_FILE, "Error opening sound device",
2946 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002947 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002948 }
2949 } else {
2950 /* Null-audio */
Benny Prijonoc45d9512010-12-10 11:04:30 +00002951 status = pjsua_set_snd_dev(pjsua_var.cap_dev,
2952 pjsua_var.play_dev);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002953 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002954 pjsua_perror(THIS_FILE, "Error opening sound device",
2955 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002956 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002957 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002958 }
Benny Prijono2d647722011-07-13 03:05:22 +00002959 } else if (pjsua_var.no_snd) {
2960 if (!pjsua_var.snd_is_on) {
2961 pjsua_var.snd_is_on = PJ_TRUE;
2962 /* Notify app */
2963 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2964 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2965 }
2966 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002967 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002968
Benny Prijonof798e502009-03-09 13:08:16 +00002969 } else {
2970 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002971
Benny Prijonof798e502009-03-09 13:08:16 +00002972 /* Create sound port if none is instantiated */
2973 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2974 !pjsua_var.no_snd)
2975 {
2976 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002977
Benny Prijonof798e502009-03-09 13:08:16 +00002978 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
2979 if (status != PJ_SUCCESS) {
2980 pjsua_perror(THIS_FILE, "Error opening sound device", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002981 goto on_return;
Benny Prijonof798e502009-03-09 13:08:16 +00002982 }
Benny Prijono2d647722011-07-13 03:05:22 +00002983 } else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) {
2984 pjsua_var.snd_is_on = PJ_TRUE;
2985 /* Notify app */
2986 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2987 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2988 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002989 }
Benny Prijonof798e502009-03-09 13:08:16 +00002990 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002991
Benny Prijonob90fd382011-09-18 14:59:56 +00002992 status = pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
2993
2994on_return:
2995 pj_log_pop_indent();
2996 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002997}
2998
2999
3000/*
3001 * Disconnect media flow from the source to destination port.
3002 */
3003PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
3004 pjsua_conf_port_id sink)
3005{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003006 pj_status_t status;
3007
Benny Prijonob90fd382011-09-18 14:59:56 +00003008 PJ_LOG(4,(THIS_FILE, "%s disconnect: %d -x- %d",
3009 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
3010 source, sink));
3011 pj_log_push_indent();
3012
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003013 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00003014 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003015
Benny Prijonob90fd382011-09-18 14:59:56 +00003016 pj_log_pop_indent();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003017 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003018}
3019
3020
Benny Prijono6dd967c2006-12-26 02:27:14 +00003021/*
3022 * Adjust the signal level to be transmitted from the bridge to the
3023 * specified port by making it louder or quieter.
3024 */
3025PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
3026 float level)
3027{
3028 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
3029 (int)((level-1) * 128));
3030}
3031
3032/*
3033 * Adjust the signal level to be received from the specified port (to
3034 * the bridge) by making it louder or quieter.
3035 */
3036PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
3037 float level)
3038{
3039 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
3040 (int)((level-1) * 128));
3041}
3042
3043
3044/*
3045 * Get last signal level transmitted to or received from the specified port.
3046 */
3047PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
3048 unsigned *tx_level,
3049 unsigned *rx_level)
3050{
3051 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
3052 tx_level, rx_level);
3053}
3054
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003055/*****************************************************************************
3056 * File player.
3057 */
3058
Benny Prijonod5696da2007-07-17 16:25:45 +00003059static char* get_basename(const char *path, unsigned len)
3060{
3061 char *p = ((char*)path) + len;
3062
3063 if (len==0)
3064 return p;
3065
Benny Prijono1f61a8f2007-08-16 10:11:44 +00003066 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00003067
3068 return (p==path) ? p : p+1;
3069}
3070
3071
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003072/*
3073 * Create a file player, and automatically connect this player to
3074 * the conference bridge.
3075 */
3076PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
3077 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003078 pjsua_player_id *p_id)
3079{
3080 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003081 char path[PJ_MAXPATH];
Benny Prijonob90fd382011-09-18 14:59:56 +00003082 pj_pool_t *pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003083 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00003084 pj_status_t status = PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003085
3086 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
3087 return PJ_ETOOMANY;
3088
Benny Prijonob90fd382011-09-18 14:59:56 +00003089 PJ_LOG(4,(THIS_FILE, "Creating file player: %.*s..",
3090 (int)filename->slen, filename->ptr));
3091 pj_log_push_indent();
3092
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003093 PJSUA_LOCK();
3094
3095 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
3096 if (pjsua_var.player[file_id].port == NULL)
3097 break;
3098 }
3099
3100 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
3101 /* This is unexpected */
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003102 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00003103 status = PJ_EBUG;
3104 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003105 }
3106
3107 pj_memcpy(path, filename->ptr, filename->slen);
3108 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00003109
3110 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
3111 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003112 status = PJ_ENOMEM;
3113 goto on_error;
Benny Prijonod5696da2007-07-17 16:25:45 +00003114 }
3115
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00003116 status = pjmedia_wav_player_port_create(
3117 pool, path,
3118 pjsua_var.mconf_cfg.samples_per_frame *
Benny Prijonoc45d9512010-12-10 11:04:30 +00003119 1000 / pjsua_var.media_cfg.channel_count /
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00003120 pjsua_var.media_cfg.clock_rate,
3121 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003122 if (status != PJ_SUCCESS) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003123 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003124 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003125 }
3126
Benny Prijono5297af92008-03-18 13:40:40 +00003127 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003128 port, filename, &slot);
3129 if (status != PJ_SUCCESS) {
3130 pjmedia_port_destroy(port);
Benny Prijono32e4f492007-01-24 00:44:26 +00003131 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
3132 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003133 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003134 }
3135
Benny Prijonoa66c3312007-01-21 23:12:40 +00003136 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00003137 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003138 pjsua_var.player[file_id].port = port;
3139 pjsua_var.player[file_id].slot = slot;
3140
3141 if (p_id) *p_id = file_id;
3142
3143 ++pjsua_var.player_cnt;
3144
3145 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003146
3147 PJ_LOG(4,(THIS_FILE, "Player created, id=%d, slot=%d", file_id, slot));
3148
3149 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003150 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003151
3152on_error:
3153 PJSUA_UNLOCK();
3154 if (pool) pj_pool_release(pool);
3155 pj_log_pop_indent();
3156 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003157}
3158
3159
3160/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00003161 * Create a file playlist media port, and automatically add the port
3162 * to the conference bridge.
3163 */
3164PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
3165 unsigned file_count,
3166 const pj_str_t *label,
3167 unsigned options,
3168 pjsua_player_id *p_id)
3169{
3170 unsigned slot, file_id, ptime;
Benny Prijonob90fd382011-09-18 14:59:56 +00003171 pj_pool_t *pool = NULL;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003172 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00003173 pj_status_t status = PJ_SUCCESS;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003174
3175 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
3176 return PJ_ETOOMANY;
3177
Benny Prijonob90fd382011-09-18 14:59:56 +00003178 PJ_LOG(4,(THIS_FILE, "Creating playlist with %d file(s)..", file_count));
3179 pj_log_push_indent();
3180
Benny Prijonoa66c3312007-01-21 23:12:40 +00003181 PJSUA_LOCK();
3182
3183 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
3184 if (pjsua_var.player[file_id].port == NULL)
3185 break;
3186 }
3187
3188 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
3189 /* This is unexpected */
Benny Prijonoa66c3312007-01-21 23:12:40 +00003190 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00003191 status = PJ_EBUG;
3192 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003193 }
3194
3195
3196 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
3197 pjsua_var.media_cfg.clock_rate;
3198
Benny Prijonod5696da2007-07-17 16:25:45 +00003199 pool = pjsua_pool_create("playlist", 1000, 1000);
3200 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003201 status = PJ_ENOMEM;
3202 goto on_error;
Benny Prijonod5696da2007-07-17 16:25:45 +00003203 }
3204
3205 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00003206 file_names, file_count,
3207 ptime, options, 0, &port);
3208 if (status != PJ_SUCCESS) {
Benny Prijonoa66c3312007-01-21 23:12:40 +00003209 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003210 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003211 }
3212
Benny Prijonod5696da2007-07-17 16:25:45 +00003213 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00003214 port, &port->info.name, &slot);
3215 if (status != PJ_SUCCESS) {
3216 pjmedia_port_destroy(port);
Benny Prijonoa66c3312007-01-21 23:12:40 +00003217 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003218 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003219 }
3220
3221 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00003222 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003223 pjsua_var.player[file_id].port = port;
3224 pjsua_var.player[file_id].slot = slot;
3225
3226 if (p_id) *p_id = file_id;
3227
3228 ++pjsua_var.player_cnt;
3229
3230 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003231
3232 PJ_LOG(4,(THIS_FILE, "Playlist created, id=%d, slot=%d", file_id, slot));
3233
3234 pj_log_pop_indent();
3235
Benny Prijonoa66c3312007-01-21 23:12:40 +00003236 return PJ_SUCCESS;
3237
Benny Prijonob90fd382011-09-18 14:59:56 +00003238on_error:
3239 PJSUA_UNLOCK();
3240 if (pool) pj_pool_release(pool);
3241 pj_log_pop_indent();
3242
3243 return status;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003244}
3245
3246
3247/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003248 * Get conference port ID associated with player.
3249 */
3250PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
3251{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003252 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003253 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
3254
3255 return pjsua_var.player[id].slot;
3256}
3257
Benny Prijono469b1522006-12-26 03:05:17 +00003258/*
3259 * Get the media port for the player.
3260 */
Benny Prijonobe41d862008-01-18 13:24:28 +00003261PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00003262 pjmedia_port **p_port)
3263{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003264 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00003265 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
3266 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
3267
3268 *p_port = pjsua_var.player[id].port;
3269
3270 return PJ_SUCCESS;
3271}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003272
3273/*
3274 * Set playback position.
3275 */
3276PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
3277 pj_uint32_t samples)
3278{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003279 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003280 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00003281 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003282
3283 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
3284}
3285
3286
3287/*
3288 * Close the file, remove the player from the bridge, and free
3289 * resources associated with the file player.
3290 */
3291PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
3292{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003293 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003294 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
3295
Benny Prijonob90fd382011-09-18 14:59:56 +00003296 PJ_LOG(4,(THIS_FILE, "Destroying player %d..", id));
3297 pj_log_push_indent();
3298
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003299 PJSUA_LOCK();
3300
3301 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00003302 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003303 pjmedia_port_destroy(pjsua_var.player[id].port);
3304 pjsua_var.player[id].port = NULL;
3305 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00003306 pj_pool_release(pjsua_var.player[id].pool);
3307 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003308 pjsua_var.player_cnt--;
3309 }
3310
3311 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003312 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003313
3314 return PJ_SUCCESS;
3315}
3316
3317
3318/*****************************************************************************
3319 * File recorder.
3320 */
3321
3322/*
3323 * Create a file recorder, and automatically connect this recorder to
3324 * the conference bridge.
3325 */
3326PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00003327 unsigned enc_type,
3328 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003329 pj_ssize_t max_size,
3330 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003331 pjsua_recorder_id *p_id)
3332{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003333 enum Format
3334 {
3335 FMT_UNKNOWN,
3336 FMT_WAV,
3337 FMT_MP3,
3338 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003339 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003340 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003341 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00003342 int file_format;
Benny Prijonob90fd382011-09-18 14:59:56 +00003343 pj_pool_t *pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003344 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00003345 pj_status_t status = PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003346
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003347 /* Filename must present */
3348 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
3349
Benny Prijono00cae612006-07-31 15:19:36 +00003350 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003351 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00003352
Benny Prijono8f310522006-10-20 11:08:49 +00003353 /* Don't support encoding type at present */
3354 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00003355
Benny Prijonob90fd382011-09-18 14:59:56 +00003356 PJ_LOG(4,(THIS_FILE, "Creating recorder %.*s..",
3357 (int)filename->slen, filename->ptr));
3358 pj_log_push_indent();
3359
3360 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) {
3361 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003362 return PJ_ETOOMANY;
Benny Prijonob90fd382011-09-18 14:59:56 +00003363 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003364
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003365 /* Determine the file format */
3366 ext.ptr = filename->ptr + filename->slen - 4;
3367 ext.slen = 4;
3368
3369 if (pj_stricmp2(&ext, ".wav") == 0)
3370 file_format = FMT_WAV;
3371 else if (pj_stricmp2(&ext, ".mp3") == 0)
3372 file_format = FMT_MP3;
3373 else {
3374 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
3375 "determine file format for %.*s",
3376 (int)filename->slen, filename->ptr));
Benny Prijonob90fd382011-09-18 14:59:56 +00003377 pj_log_pop_indent();
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003378 return PJ_ENOTSUP;
3379 }
3380
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003381 PJSUA_LOCK();
3382
3383 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
3384 if (pjsua_var.recorder[file_id].port == NULL)
3385 break;
3386 }
3387
3388 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
3389 /* This is unexpected */
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003390 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00003391 status = PJ_EBUG;
3392 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003393 }
3394
3395 pj_memcpy(path, filename->ptr, filename->slen);
3396 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003397
Benny Prijonod5696da2007-07-17 16:25:45 +00003398 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
3399 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003400 status = PJ_ENOMEM;
3401 goto on_return;
Benny Prijonod5696da2007-07-17 16:25:45 +00003402 }
3403
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003404 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00003405 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003406 pjsua_var.media_cfg.clock_rate,
3407 pjsua_var.mconf_cfg.channel_count,
3408 pjsua_var.mconf_cfg.samples_per_frame,
3409 pjsua_var.mconf_cfg.bits_per_sample,
3410 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003411 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00003412 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003413 port = NULL;
3414 status = PJ_ENOTSUP;
3415 }
3416
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003417 if (status != PJ_SUCCESS) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003418 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003419 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003420 }
3421
Benny Prijonod5696da2007-07-17 16:25:45 +00003422 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003423 port, filename, &slot);
3424 if (status != PJ_SUCCESS) {
3425 pjmedia_port_destroy(port);
Benny Prijonob90fd382011-09-18 14:59:56 +00003426 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003427 }
3428
3429 pjsua_var.recorder[file_id].port = port;
3430 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00003431 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003432
3433 if (p_id) *p_id = file_id;
3434
3435 ++pjsua_var.rec_cnt;
3436
3437 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003438
3439 PJ_LOG(4,(THIS_FILE, "Recorder created, id=%d, slot=%d", file_id, slot));
3440
3441 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003442 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003443
3444on_return:
3445 PJSUA_UNLOCK();
3446 if (pool) pj_pool_release(pool);
3447 pj_log_pop_indent();
3448 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003449}
3450
3451
3452/*
3453 * Get conference port associated with recorder.
3454 */
3455PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
3456{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003457 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3458 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003459 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3460
3461 return pjsua_var.recorder[id].slot;
3462}
3463
Benny Prijono469b1522006-12-26 03:05:17 +00003464/*
3465 * Get the media port for the recorder.
3466 */
3467PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
3468 pjmedia_port **p_port)
3469{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003470 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3471 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00003472 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3473 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
3474
3475 *p_port = pjsua_var.recorder[id].port;
3476 return PJ_SUCCESS;
3477}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003478
3479/*
3480 * Destroy recorder (this will complete recording).
3481 */
3482PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
3483{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003484 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3485 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003486 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3487
Benny Prijonob90fd382011-09-18 14:59:56 +00003488 PJ_LOG(4,(THIS_FILE, "Destroying recorder %d..", id));
3489 pj_log_push_indent();
3490
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003491 PJSUA_LOCK();
3492
3493 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00003494 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003495 pjmedia_port_destroy(pjsua_var.recorder[id].port);
3496 pjsua_var.recorder[id].port = NULL;
3497 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00003498 pj_pool_release(pjsua_var.recorder[id].pool);
3499 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003500 pjsua_var.rec_cnt--;
3501 }
3502
3503 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003504 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003505
3506 return PJ_SUCCESS;
3507}
3508
3509
3510/*****************************************************************************
3511 * Sound devices.
3512 */
3513
3514/*
3515 * Enum sound devices.
3516 */
Benny Prijonof798e502009-03-09 13:08:16 +00003517
3518PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003519 unsigned *count)
3520{
3521 unsigned i, dev_count;
3522
Benny Prijono10454dc2009-02-21 14:21:59 +00003523 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003524
3525 if (dev_count > *count) dev_count = *count;
3526
3527 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00003528 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003529
Benny Prijono10454dc2009-02-21 14:21:59 +00003530 status = pjmedia_aud_dev_get_info(i, &info[i]);
3531 if (status != PJ_SUCCESS)
3532 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003533 }
3534
3535 *count = dev_count;
3536
3537 return PJ_SUCCESS;
3538}
Benny Prijonof798e502009-03-09 13:08:16 +00003539
3540
Benny Prijono10454dc2009-02-21 14:21:59 +00003541PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
3542 unsigned *count)
3543{
3544 unsigned i, dev_count;
3545
3546 dev_count = pjmedia_aud_dev_count();
3547
3548 if (dev_count > *count) dev_count = *count;
3549 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
3550
3551 for (i=0; i<dev_count; ++i) {
3552 pjmedia_aud_dev_info ai;
3553 pj_status_t status;
3554
3555 status = pjmedia_aud_dev_get_info(i, &ai);
3556 if (status != PJ_SUCCESS)
3557 return status;
3558
3559 strncpy(info[i].name, ai.name, sizeof(info[i].name));
3560 info[i].name[sizeof(info[i].name)-1] = '\0';
3561 info[i].input_count = ai.input_count;
3562 info[i].output_count = ai.output_count;
3563 info[i].default_samples_per_sec = ai.default_samples_per_sec;
3564 }
3565
3566 *count = dev_count;
3567
3568 return PJ_SUCCESS;
3569}
Benny Prijono10454dc2009-02-21 14:21:59 +00003570
Benny Prijonof798e502009-03-09 13:08:16 +00003571/* Create audio device parameter to open the device */
3572static pj_status_t create_aud_param(pjmedia_aud_param *param,
3573 pjmedia_aud_dev_index capture_dev,
3574 pjmedia_aud_dev_index playback_dev,
3575 unsigned clock_rate,
3576 unsigned channel_count,
3577 unsigned samples_per_frame,
3578 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003579{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003580 pj_status_t status;
3581
Benny Prijono96e74f32009-02-22 12:00:12 +00003582 /* Normalize device ID with new convention about default device ID */
3583 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
3584 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
3585
Benny Prijono10454dc2009-02-21 14:21:59 +00003586 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00003587 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00003588 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00003589 pjsua_perror(THIS_FILE, "Error retrieving default audio "
3590 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00003591 return status;
3592 }
Benny Prijonof798e502009-03-09 13:08:16 +00003593 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
3594 param->rec_id = capture_dev;
3595 param->play_id = playback_dev;
3596 param->clock_rate = clock_rate;
3597 param->channel_count = channel_count;
3598 param->samples_per_frame = samples_per_frame;
3599 param->bits_per_sample = bits_per_sample;
3600
3601 /* Update the setting with user preference */
3602#define update_param(cap, field) \
3603 if (pjsua_var.aud_param.flags & cap) { \
3604 param->flags |= cap; \
3605 param->field = pjsua_var.aud_param.field; \
3606 }
3607 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3608 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3609 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3610 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3611#undef update_param
3612
Benny Prijono10454dc2009-02-21 14:21:59 +00003613 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00003614 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
3615 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
3616 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
3617 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
3618
Benny Prijono10454dc2009-02-21 14:21:59 +00003619 /* EC settings */
3620 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00003621 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
3622 param->ec_enabled = PJ_TRUE;
3623 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00003624 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00003625 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00003626 }
3627
Benny Prijonof798e502009-03-09 13:08:16 +00003628 return PJ_SUCCESS;
3629}
Benny Prijono26056d82006-10-11 16:03:41 +00003630
Benny Prijonof798e502009-03-09 13:08:16 +00003631/* Internal: the first time the audio device is opened (during app
3632 * startup), retrieve the audio settings such as volume level
3633 * so that aud_get_settings() will work.
3634 */
3635static pj_status_t update_initial_aud_param()
3636{
3637 pjmedia_aud_stream *strm;
3638 pjmedia_aud_param param;
3639 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00003640
Benny Prijonof798e502009-03-09 13:08:16 +00003641 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00003642
Benny Prijonof798e502009-03-09 13:08:16 +00003643 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00003644
Benny Prijonof798e502009-03-09 13:08:16 +00003645 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003646 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00003647 pjsua_perror(THIS_FILE, "Error audio stream "
3648 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003649 return status;
3650 }
3651
Benny Prijonof798e502009-03-09 13:08:16 +00003652#define update_saved_param(cap, field) \
3653 if (param.flags & cap) { \
3654 pjsua_var.aud_param.flags |= cap; \
3655 pjsua_var.aud_param.field = param.field; \
3656 }
3657
3658 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3659 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3660 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3661 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3662#undef update_saved_param
3663
3664 return PJ_SUCCESS;
3665}
3666
3667/* Get format name */
3668static const char *get_fmt_name(pj_uint32_t id)
3669{
3670 static char name[8];
3671
3672 if (id == PJMEDIA_FORMAT_L16)
3673 return "PCM";
3674 pj_memcpy(name, &id, 4);
3675 name[4] = '\0';
3676 return name;
3677}
3678
3679/* Open sound device with the setting. */
Sauw Ming98766c72011-03-11 06:57:24 +00003680static pj_status_t open_snd_dev(pjmedia_snd_port_param *param)
Benny Prijonof798e502009-03-09 13:08:16 +00003681{
3682 pjmedia_port *conf_port;
3683 pj_status_t status;
3684
3685 PJ_ASSERT_RETURN(param, PJ_EINVAL);
3686
3687 /* Check if NULL sound device is used */
Sauw Ming98766c72011-03-11 06:57:24 +00003688 if (NULL_SND_DEV_ID==param->base.rec_id ||
3689 NULL_SND_DEV_ID==param->base.play_id)
3690 {
Benny Prijonof798e502009-03-09 13:08:16 +00003691 return pjsua_set_null_snd_dev();
3692 }
3693
3694 /* Close existing sound port */
3695 close_snd_dev();
3696
Benny Prijono2d647722011-07-13 03:05:22 +00003697 /* Notify app */
3698 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3699 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
3700 }
3701
Benny Prijonof798e502009-03-09 13:08:16 +00003702 /* Create memory pool for sound device. */
3703 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3704 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3705
3706
3707 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
Sauw Ming98766c72011-03-11 06:57:24 +00003708 get_fmt_name(param->base.ext_fmt.id),
3709 param->base.clock_rate, param->base.channel_count,
3710 param->base.samples_per_frame / param->base.channel_count *
3711 1000 / param->base.clock_rate));
Benny Prijonob90fd382011-09-18 14:59:56 +00003712 pj_log_push_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003713
3714 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
Sauw Ming98766c72011-03-11 06:57:24 +00003715 param, &pjsua_var.snd_port);
Benny Prijonof798e502009-03-09 13:08:16 +00003716 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00003717 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003718
3719 /* Get the port0 of the conference bridge. */
3720 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3721 pj_assert(conf_port != NULL);
3722
3723 /* For conference bridge, resample if necessary if the bridge's
3724 * clock rate is different than the sound device's clock rate.
3725 */
3726 if (!pjsua_var.is_mswitch &&
Sauw Ming98766c72011-03-11 06:57:24 +00003727 param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM &&
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003728 PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate)
Benny Prijonof798e502009-03-09 13:08:16 +00003729 {
3730 pjmedia_port *resample_port;
3731 unsigned resample_opt = 0;
3732
3733 if (pjsua_var.media_cfg.quality >= 3 &&
3734 pjsua_var.media_cfg.quality <= 4)
3735 {
3736 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
3737 }
3738 else if (pjsua_var.media_cfg.quality < 3) {
3739 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
3740 }
3741
3742 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
3743 conf_port,
Sauw Ming98766c72011-03-11 06:57:24 +00003744 param->base.clock_rate,
Benny Prijonof798e502009-03-09 13:08:16 +00003745 resample_opt,
3746 &resample_port);
3747 if (status != PJ_SUCCESS) {
3748 char errmsg[PJ_ERR_MSG_SIZE];
3749 pj_strerror(status, errmsg, sizeof(errmsg));
3750 PJ_LOG(4, (THIS_FILE,
3751 "Error creating resample port: %s",
3752 errmsg));
3753 close_snd_dev();
Benny Prijonob90fd382011-09-18 14:59:56 +00003754 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003755 }
3756
3757 conf_port = resample_port;
3758 }
3759
3760 /* Otherwise for audio switchboard, the switch's port0 setting is
3761 * derived from the sound device setting, so update the setting.
3762 */
3763 if (pjsua_var.is_mswitch) {
Nanang Izzuddin0c2aab72011-12-20 09:52:19 +00003764 if (param->base.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
3765 conf_port->info.fmt = param->base.ext_fmt;
3766 } else {
3767 unsigned bps, ptime_usec;
3768 bps = param->base.clock_rate * param->base.bits_per_sample;
3769 ptime_usec = param->base.samples_per_frame /
3770 param->base.channel_count * 1000000 /
3771 param->base.clock_rate;
3772 pjmedia_format_init_audio(&conf_port->info.fmt,
3773 PJMEDIA_FORMAT_PCM,
3774 param->base.clock_rate,
3775 param->base.channel_count,
3776 param->base.bits_per_sample,
3777 ptime_usec,
3778 bps, bps);
3779 }
Benny Prijonof798e502009-03-09 13:08:16 +00003780 }
3781
Benny Prijonoc45d9512010-12-10 11:04:30 +00003782
Benny Prijonof798e502009-03-09 13:08:16 +00003783 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00003784 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
3785 conf_port );
3786 if (status != PJ_SUCCESS) {
3787 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
3788 "sound device", status);
3789 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3790 pjsua_var.snd_port = NULL;
Benny Prijonob90fd382011-09-18 14:59:56 +00003791 goto on_error;
Benny Prijono52a93912006-08-04 20:54:37 +00003792 }
3793
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003794 /* Save the device IDs */
Sauw Ming98766c72011-03-11 06:57:24 +00003795 pjsua_var.cap_dev = param->base.rec_id;
3796 pjsua_var.play_dev = param->base.play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003797
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003798 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00003799 {
3800 pjmedia_aud_dev_info rec_info;
3801 pjmedia_aud_stream *strm;
3802 pjmedia_aud_param si;
3803 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003804
Benny Prijonof798e502009-03-09 13:08:16 +00003805 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3806 status = pjmedia_aud_stream_get_param(strm, &si);
3807 if (status == PJ_SUCCESS)
3808 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00003809
Benny Prijonof798e502009-03-09 13:08:16 +00003810 if (status==PJ_SUCCESS) {
Sauw Ming98766c72011-03-11 06:57:24 +00003811 if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) {
Benny Prijonof798e502009-03-09 13:08:16 +00003812 char tmp_buf[128];
3813 int tmp_buf_len = sizeof(tmp_buf);
3814
3815 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
3816 "%s (%dKHz)",
3817 rec_info.name,
Sauw Ming98766c72011-03-11 06:57:24 +00003818 param->base.clock_rate/1000);
Benny Prijonof798e502009-03-09 13:08:16 +00003819 pj_strset(&tmp, tmp_buf, tmp_buf_len);
3820 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
3821 } else {
3822 pjmedia_conf_set_port0_name(pjsua_var.mconf,
3823 pj_cstr(&tmp, rec_info.name));
3824 }
3825 }
3826
3827 /* Any error is not major, let it through */
3828 status = PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003829 }
Benny Prijonof798e502009-03-09 13:08:16 +00003830
3831 /* If this is the first time the audio device is open, retrieve some
3832 * settings from the device (such as volume settings) so that the
3833 * pjsua_snd_get_setting() work.
3834 */
3835 if (pjsua_var.aud_open_cnt == 0) {
3836 update_initial_aud_param();
3837 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00003838 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003839
Benny Prijonob90fd382011-09-18 14:59:56 +00003840 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003841 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003842
3843on_error:
3844 pj_log_pop_indent();
3845 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00003846}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003847
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003848
Benny Prijonof798e502009-03-09 13:08:16 +00003849/* Close existing sound device */
3850static void close_snd_dev(void)
3851{
Benny Prijonob90fd382011-09-18 14:59:56 +00003852 pj_log_push_indent();
3853
Benny Prijono2d647722011-07-13 03:05:22 +00003854 /* Notify app */
3855 if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3856 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0);
3857 }
3858
Benny Prijonof798e502009-03-09 13:08:16 +00003859 /* Close sound device */
3860 if (pjsua_var.snd_port) {
3861 pjmedia_aud_dev_info cap_info, play_info;
3862 pjmedia_aud_stream *strm;
3863 pjmedia_aud_param param;
3864
3865 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3866 pjmedia_aud_stream_get_param(strm, &param);
3867
3868 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
3869 cap_info.name[0] = '\0';
3870 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
3871 play_info.name[0] = '\0';
3872
3873 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
3874 "%s sound capture device",
3875 play_info.name, cap_info.name));
3876
3877 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
3878 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3879 pjsua_var.snd_port = NULL;
3880 }
3881
3882 /* Close null sound device */
3883 if (pjsua_var.null_snd) {
3884 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
3885 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
3886 pjsua_var.null_snd = NULL;
3887 }
3888
3889 if (pjsua_var.snd_pool)
3890 pj_pool_release(pjsua_var.snd_pool);
3891 pjsua_var.snd_pool = NULL;
Benny Prijono2d647722011-07-13 03:05:22 +00003892 pjsua_var.snd_is_on = PJ_FALSE;
Benny Prijonob90fd382011-09-18 14:59:56 +00003893
3894 pj_log_pop_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003895}
3896
3897
3898/*
3899 * Select or change sound device. Application may call this function at
3900 * any time to replace current sound device.
3901 */
3902PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
3903 int playback_dev)
3904{
3905 unsigned alt_cr_cnt = 1;
3906 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
3907 unsigned i;
3908 pj_status_t status = -1;
3909
Benny Prijonob90fd382011-09-18 14:59:56 +00003910 PJ_LOG(4,(THIS_FILE, "Set sound device: capture=%d, playback=%d",
3911 capture_dev, playback_dev));
3912 pj_log_push_indent();
3913
Benny Prijono23ea21a2009-06-03 12:43:06 +00003914 /* Null-sound */
3915 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003916 status = pjsua_set_null_snd_dev();
3917 pj_log_pop_indent();
3918 return status;
Benny Prijono23ea21a2009-06-03 12:43:06 +00003919 }
3920
Benny Prijonof798e502009-03-09 13:08:16 +00003921 /* Set default clock rate */
3922 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
3923 if (alt_cr[0] == 0)
3924 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
3925
3926 /* Allow retrying of different clock rate if we're using conference
3927 * bridge (meaning audio format is always PCM), otherwise lock on
3928 * to one clock rate.
3929 */
3930 if (pjsua_var.is_mswitch) {
3931 alt_cr_cnt = 1;
3932 } else {
3933 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
3934 }
3935
3936 /* Attempts to open the sound device with different clock rates */
3937 for (i=0; i<alt_cr_cnt; ++i) {
Sauw Ming98766c72011-03-11 06:57:24 +00003938 pjmedia_snd_port_param param;
Benny Prijonof798e502009-03-09 13:08:16 +00003939 unsigned samples_per_frame;
3940
3941 /* Create the default audio param */
3942 samples_per_frame = alt_cr[i] *
3943 pjsua_var.media_cfg.audio_frame_ptime *
3944 pjsua_var.media_cfg.channel_count / 1000;
Sauw Ming98766c72011-03-11 06:57:24 +00003945 status = create_aud_param(&param.base, capture_dev, playback_dev,
Benny Prijonof798e502009-03-09 13:08:16 +00003946 alt_cr[i], pjsua_var.media_cfg.channel_count,
3947 samples_per_frame, 16);
3948 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00003949 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003950
3951 /* Open! */
Sauw Ming98766c72011-03-11 06:57:24 +00003952 param.options = 0;
Benny Prijonof798e502009-03-09 13:08:16 +00003953 status = open_snd_dev(&param);
3954 if (status == PJ_SUCCESS)
3955 break;
3956 }
3957
3958 if (status != PJ_SUCCESS) {
3959 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003960 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003961 }
3962
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003963 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00003964 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003965
Benny Prijonob90fd382011-09-18 14:59:56 +00003966 pj_log_pop_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003967 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003968
3969on_error:
3970 pj_log_pop_indent();
3971 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003972}
3973
3974
3975/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00003976 * Get currently active sound devices. If sound devices has not been created
3977 * (for example when pjsua_start() is not called), it is possible that
3978 * the function returns PJ_SUCCESS with -1 as device IDs.
3979 */
3980PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
3981 int *playback_dev)
3982{
3983 if (capture_dev) {
3984 *capture_dev = pjsua_var.cap_dev;
3985 }
3986 if (playback_dev) {
3987 *playback_dev = pjsua_var.play_dev;
3988 }
3989
3990 return PJ_SUCCESS;
3991}
3992
3993
3994/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003995 * Use null sound device.
3996 */
3997PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
3998{
3999 pjmedia_port *conf_port;
4000 pj_status_t status;
4001
Benny Prijonob90fd382011-09-18 14:59:56 +00004002 PJ_LOG(4,(THIS_FILE, "Setting null sound device.."));
4003 pj_log_push_indent();
4004
4005
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004006 /* Close existing sound device */
4007 close_snd_dev();
4008
Benny Prijono2d647722011-07-13 03:05:22 +00004009 /* Notify app */
4010 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
4011 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
4012 }
4013
Nanang Izzuddin68559c32008-06-13 17:01:46 +00004014 /* Create memory pool for sound device. */
4015 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
4016 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
4017
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004018 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
4019
4020 /* Get the port0 of the conference bridge. */
4021 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
4022 pj_assert(conf_port != NULL);
4023
4024 /* Create master port, connecting port0 of the conference bridge to
4025 * a null port.
4026 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00004027 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004028 conf_port, 0, &pjsua_var.null_snd);
4029 if (status != PJ_SUCCESS) {
4030 pjsua_perror(THIS_FILE, "Unable to create null sound device",
4031 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00004032 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004033 return status;
4034 }
4035
4036 /* Start the master port */
4037 status = pjmedia_master_port_start(pjsua_var.null_snd);
4038 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
4039
Nanang Izzuddin68559c32008-06-13 17:01:46 +00004040 pjsua_var.cap_dev = NULL_SND_DEV_ID;
4041 pjsua_var.play_dev = NULL_SND_DEV_ID;
4042
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00004043 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00004044 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00004045
Benny Prijonob90fd382011-09-18 14:59:56 +00004046 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004047 return PJ_SUCCESS;
4048}
4049
4050
Benny Prijonoe909eac2006-07-27 22:04:56 +00004051
4052/*
4053 * Use no device!
4054 */
4055PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
4056{
4057 /* Close existing sound device */
4058 close_snd_dev();
4059
4060 pjsua_var.no_snd = PJ_TRUE;
4061 return pjmedia_conf_get_master_port(pjsua_var.mconf);
4062}
4063
4064
Benny Prijonof20687a2006-08-04 18:27:19 +00004065/*
4066 * Configure the AEC settings of the sound port.
4067 */
Benny Prijono5da50432006-08-07 10:24:52 +00004068PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00004069{
4070 pjsua_var.media_cfg.ec_tail_len = tail_ms;
4071
4072 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00004073 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
4074 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00004075
4076 return PJ_SUCCESS;
4077}
4078
4079
4080/*
4081 * Get current AEC tail length.
4082 */
Benny Prijono22dfe592006-08-06 12:07:13 +00004083PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00004084{
4085 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
4086 return PJ_SUCCESS;
4087}
4088
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00004089
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00004090/*
Benny Prijonof798e502009-03-09 13:08:16 +00004091 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00004092 */
Benny Prijonof798e502009-03-09 13:08:16 +00004093PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00004094{
Benny Prijonof798e502009-03-09 13:08:16 +00004095 return pjsua_var.snd_port != NULL;
4096}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00004097
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00004098
Benny Prijonof798e502009-03-09 13:08:16 +00004099/*
4100 * Configure sound device setting to the sound device being used.
4101 */
4102PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
4103 const void *pval,
4104 pj_bool_t keep)
4105{
Benny Prijono09b0ff62009-03-10 12:07:51 +00004106 pj_status_t status;
4107
Benny Prijonof798e502009-03-09 13:08:16 +00004108 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00004109 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00004110 return PJMEDIA_EAUD_INVCAP;
4111 }
4112
Benny Prijono09b0ff62009-03-10 12:07:51 +00004113 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00004114 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00004115 pjmedia_aud_stream *strm;
4116
4117 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00004118 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00004119 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00004120 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00004121 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00004122
4123 if (status != PJ_SUCCESS)
4124 return status;
4125
4126 /* Save in internal param for later device open */
4127 if (keep) {
4128 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
4129 cap, pval);
4130 }
4131
4132 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00004133}
4134
4135/*
4136 * Retrieve a sound device setting.
4137 */
4138PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
4139 void *pval)
4140{
4141 /* If sound device has never been opened before, open it to
4142 * retrieve the initial setting from the device (e.g. audio
4143 * volume)
4144 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00004145 if (pjsua_var.aud_open_cnt==0) {
4146 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00004147 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00004148 close_snd_dev();
4149 }
Benny Prijonof798e502009-03-09 13:08:16 +00004150
4151 if (pjsua_snd_is_active()) {
4152 /* Sound is active, retrieve from device directly */
4153 pjmedia_aud_stream *strm;
4154
4155 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
4156 return pjmedia_aud_stream_get_cap(strm, cap, pval);
4157 } else {
4158 /* Otherwise retrieve from internal param */
4159 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
4160 cap, pval);
4161 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00004162}
4163
4164
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004165/*****************************************************************************
4166 * Codecs.
4167 */
4168
4169/*
4170 * Enum all supported codecs in the system.
4171 */
4172PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
4173 unsigned *p_count )
4174{
4175 pjmedia_codec_mgr *codec_mgr;
4176 pjmedia_codec_info info[32];
4177 unsigned i, count, prio[32];
4178 pj_status_t status;
4179
4180 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4181 count = PJ_ARRAY_SIZE(info);
4182 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
4183 if (status != PJ_SUCCESS) {
4184 *p_count = 0;
4185 return status;
4186 }
4187
4188 if (count > *p_count) count = *p_count;
4189
4190 for (i=0; i<count; ++i) {
Nanang Izzuddin56b2ce42011-04-06 13:55:01 +00004191 pj_bzero(&id[i], sizeof(pjsua_codec_info));
4192
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004193 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
4194 id[i].codec_id = pj_str(id[i].buf_);
4195 id[i].priority = (pj_uint8_t) prio[i];
4196 }
4197
4198 *p_count = count;
4199
4200 return PJ_SUCCESS;
4201}
4202
4203
4204/*
4205 * Change codec priority.
4206 */
4207PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
4208 pj_uint8_t priority )
4209{
Benny Prijono88accae2008-06-26 15:48:14 +00004210 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004211 pjmedia_codec_mgr *codec_mgr;
4212
4213 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4214
Benny Prijono88accae2008-06-26 15:48:14 +00004215 if (codec_id->slen==1 && *codec_id->ptr=='*')
4216 codec_id = &all;
4217
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004218 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
4219 priority);
4220}
4221
4222
4223/*
4224 * Get codec parameters.
4225 */
4226PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
4227 pjmedia_codec_param *param )
4228{
Benny Prijono88accae2008-06-26 15:48:14 +00004229 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004230 const pjmedia_codec_info *info;
4231 pjmedia_codec_mgr *codec_mgr;
4232 unsigned count = 1;
4233 pj_status_t status;
4234
4235 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4236
Benny Prijono88accae2008-06-26 15:48:14 +00004237 if (codec_id->slen==1 && *codec_id->ptr=='*')
4238 codec_id = &all;
4239
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004240 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
4241 &count, &info, NULL);
4242 if (status != PJ_SUCCESS)
4243 return status;
4244
4245 if (count != 1)
Nanang Izzuddin50fae732011-03-22 09:49:23 +00004246 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004247
4248 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
4249 return status;
4250}
4251
4252
4253/*
4254 * Set codec parameters.
4255 */
Nanang Izzuddin06839e72010-01-27 11:48:31 +00004256PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004257 const pjmedia_codec_param *param)
4258{
Nanang Izzuddin06839e72010-01-27 11:48:31 +00004259 const pjmedia_codec_info *info[2];
4260 pjmedia_codec_mgr *codec_mgr;
4261 unsigned count = 2;
4262 pj_status_t status;
4263
4264 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4265
4266 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
4267 &count, info, NULL);
4268 if (status != PJ_SUCCESS)
4269 return status;
4270
4271 /* Codec ID should be specific, except for G.722.1 */
4272 if (count > 1 &&
4273 pj_strnicmp2(codec_id, "G7221/16", 8) != 0 &&
4274 pj_strnicmp2(codec_id, "G7221/32", 8) != 0)
4275 {
4276 pj_assert(!"Codec ID is not specific");
4277 return PJ_ETOOMANY;
4278 }
4279
4280 status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param);
4281 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004282}
Nanang Izzuddin50fae732011-03-22 09:49:23 +00004283
4284
Nanang Izzuddin3da4ad82011-12-07 10:43:28 +00004285pj_status_t pjsua_media_apply_xml_control(pjsua_call_id call_id,
4286 const pj_str_t *xml_st)
4287{
4288 pjsua_call *call = &pjsua_var.calls[call_id];
4289 const pj_str_t PICT_FAST_UPDATE = {"picture_fast_update", 19};
4290
4291#if PJMEDIA_HAS_VIDEO
4292 if (pj_strstr(xml_st, &PICT_FAST_UPDATE)) {
4293 unsigned i;
4294
4295 PJ_LOG(4,(THIS_FILE, "Received keyframe request via SIP INFO"));
4296
4297 for (i = 0; i < call->med_cnt; ++i) {
4298 pjsua_call_media *cm = &call->media[i];
4299 if (cm->type != PJMEDIA_TYPE_VIDEO || !cm->strm.v.stream)
4300 continue;
4301
4302 pjmedia_vid_stream_send_keyframe(cm->strm.v.stream);
4303 }
4304
4305 return PJ_SUCCESS;
4306 }
4307#endif
4308
4309 /* Just to avoid compiler warning of unused var */
4310 PJ_UNUSED_ARG(xml_st);
4311
4312 return PJ_ENOTSUP;
4313}
4314