blob: 7665e7516593bee89e49eddb3ea3a1e8c9da5ea3 [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
Benny Prijono096c56c2007-09-15 08:30:16 +0000769/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000770static void on_ice_complete(pjmedia_transport *tp,
771 pj_ice_strans_op op,
772 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000773{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000774 pjsua_call_media *call_med = (pjsua_call_media*)tp->user_data;
Benny Prijono096c56c2007-09-15 08:30:16 +0000775
Benny Prijono0bc99a92011-03-17 04:34:43 +0000776 if (!call_med)
Benny Prijonof76e1392008-06-06 14:51:48 +0000777 return;
778
779 switch (op) {
780 case PJ_ICE_STRANS_OP_INIT:
Sauw Mingc8e12942011-10-25 08:51:02 +0000781 PJSUA_LOCK();
Sauw Ming73ecfe82011-09-21 10:20:01 +0000782 call_med->tp_ready = result;
783 if (call_med->med_create_cb)
784 (*call_med->med_create_cb)(call_med, result,
785 call_med->call->secure_level, NULL);
Sauw Mingc8e12942011-10-25 08:51:02 +0000786 PJSUA_UNLOCK();
Benny Prijonof76e1392008-06-06 14:51:48 +0000787 break;
788 case PJ_ICE_STRANS_OP_NEGOTIATION:
789 if (result != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000790 call_med->state = PJSUA_CALL_MEDIA_ERROR;
791 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijonof76e1392008-06-06 14:51:48 +0000792
Benny Prijono0bc99a92011-03-17 04:34:43 +0000793 if (call_med->call && pjsua_var.ua_cfg.cb.on_call_media_state) {
794 pjsua_var.ua_cfg.cb.on_call_media_state(call_med->call->index);
Benny Prijonof76e1392008-06-06 14:51:48 +0000795 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000796 } else if (call_med->call) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000797 /* Send UPDATE if default transport address is different than
798 * what was advertised (ticket #881)
799 */
800 pjmedia_transport_info tpinfo;
801 pjmedia_ice_transport_info *ii = NULL;
802 unsigned i;
803
804 pjmedia_transport_info_init(&tpinfo);
805 pjmedia_transport_get_info(tp, &tpinfo);
806 for (i=0; i<tpinfo.specific_info_cnt; ++i) {
807 if (tpinfo.spc_info[i].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
808 ii = (pjmedia_ice_transport_info*)
809 tpinfo.spc_info[i].buffer;
810 break;
811 }
812 }
813
814 if (ii && ii->role==PJ_ICE_SESS_ROLE_CONTROLLING &&
815 pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000816 &call_med->rtp_addr))
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000817 {
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000818 pj_bool_t use_update;
819 const pj_str_t STR_UPDATE = { "UPDATE", 6 };
820 pjsip_dialog_cap_status support_update;
821 pjsip_dialog *dlg;
822
Benny Prijono0bc99a92011-03-17 04:34:43 +0000823 dlg = call_med->call->inv->dlg;
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000824 support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW,
825 NULL, &STR_UPDATE);
826 use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED);
827
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000828 PJ_LOG(4,(THIS_FILE,
829 "ICE default transport address has changed for "
Benny Prijonodb5d89d2011-10-25 13:39:06 +0000830 "call %d, sending %s",
831 call_med->call->index,
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000832 (use_update ? "UPDATE" : "re-INVITE")));
833
834 if (use_update)
Benny Prijono0bc99a92011-03-17 04:34:43 +0000835 pjsua_call_update(call_med->call->index, 0, NULL);
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000836 else
Benny Prijono0bc99a92011-03-17 04:34:43 +0000837 pjsua_call_reinvite(call_med->call->index, 0, NULL);
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000838 }
Benny Prijonof76e1392008-06-06 14:51:48 +0000839 }
840 break;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000841 case PJ_ICE_STRANS_OP_KEEP_ALIVE:
842 if (result != PJ_SUCCESS) {
843 PJ_PERROR(4,(THIS_FILE, result,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000844 "ICE keep alive failure for transport %d:%d",
845 call_med->call->index, call_med->idx));
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000846 }
Sauw Ming73ecfe82011-09-21 10:20:01 +0000847 if (pjsua_var.ua_cfg.cb.on_call_media_transport_state) {
848 pjsua_med_tp_state_info info;
849
850 pj_bzero(&info, sizeof(info));
851 info.med_idx = call_med->idx;
852 info.state = call_med->tp_st;
853 info.status = result;
854 info.ext_info = &op;
855 (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)(
856 call_med->call->index, &info);
857 }
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000858 if (pjsua_var.ua_cfg.cb.on_ice_transport_error) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000859 pjsua_call_id id = call_med->call->index;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000860 (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result,
861 NULL);
862 }
863 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000864 }
865}
866
867
Benny Prijonof76e1392008-06-06 14:51:48 +0000868/* Parse "HOST:PORT" format */
869static pj_status_t parse_host_port(const pj_str_t *host_port,
870 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000871{
Benny Prijonof76e1392008-06-06 14:51:48 +0000872 pj_str_t str_port;
873
874 str_port.ptr = pj_strchr(host_port, ':');
875 if (str_port.ptr != NULL) {
876 int iport;
877
878 host->ptr = host_port->ptr;
879 host->slen = (str_port.ptr - host->ptr);
880 str_port.ptr++;
881 str_port.slen = host_port->slen - host->slen - 1;
882 iport = (int)pj_strtoul(&str_port);
883 if (iport < 1 || iport > 65535)
884 return PJ_EINVAL;
885 *port = (pj_uint16_t)iport;
886 } else {
887 *host = *host_port;
888 *port = 0;
889 }
890
891 return PJ_SUCCESS;
892}
893
894/* Create ICE media transports (when ice is enabled) */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000895static pj_status_t create_ice_media_transport(
896 const pjsua_transport_config *cfg,
Sauw Ming73ecfe82011-09-21 10:20:01 +0000897 pjsua_call_media *call_med,
898 pj_bool_t async)
Benny Prijonof76e1392008-06-06 14:51:48 +0000899{
900 char stunip[PJ_INET6_ADDRSTRLEN];
901 pj_ice_strans_cfg ice_cfg;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000902 pjmedia_ice_cb ice_cb;
903 char name[32];
904 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000905 pj_status_t status;
906
Benny Prijonoda9785b2007-04-02 20:43:06 +0000907 /* Make sure STUN server resolution has completed */
Benny Prijonobb995fd2009-08-12 11:03:23 +0000908 status = resolve_stun_server(PJ_TRUE);
Benny Prijonoda9785b2007-04-02 20:43:06 +0000909 if (status != PJ_SUCCESS) {
910 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
911 return status;
912 }
913
Benny Prijonof76e1392008-06-06 14:51:48 +0000914 /* Create ICE stream transport configuration */
915 pj_ice_strans_cfg_default(&ice_cfg);
916 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
917 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
918 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
919
920 ice_cfg.af = pj_AF_INET();
921 ice_cfg.resolver = pjsua_var.resolver;
922
Benny Prijono329d6382009-05-29 13:04:03 +0000923 ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
924
Benny Prijonof76e1392008-06-06 14:51:48 +0000925 /* Configure STUN settings */
926 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
927 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
928 ice_cfg.stun.server = pj_str(stunip);
929 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
930 }
Benny Prijono329d6382009-05-29 13:04:03 +0000931 if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
932 ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
Benny Prijonof76e1392008-06-06 14:51:48 +0000933
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000934 /* Copy QoS setting to STUN setting */
935 ice_cfg.stun.cfg.qos_type = cfg->qos_type;
936 pj_memcpy(&ice_cfg.stun.cfg.qos_params, &cfg->qos_params,
937 sizeof(cfg->qos_params));
938
Benny Prijonof76e1392008-06-06 14:51:48 +0000939 /* Configure TURN settings */
940 if (pjsua_var.media_cfg.enable_turn) {
941 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
942 &ice_cfg.turn.server,
943 &ice_cfg.turn.port);
944 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
945 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
946 return PJ_EINVAL;
947 }
948 if (ice_cfg.turn.port == 0)
949 ice_cfg.turn.port = 3479;
950 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
951 pj_memcpy(&ice_cfg.turn.auth_cred,
952 &pjsua_var.media_cfg.turn_auth_cred,
953 sizeof(ice_cfg.turn.auth_cred));
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000954
955 /* Copy QoS setting to TURN setting */
956 ice_cfg.turn.cfg.qos_type = cfg->qos_type;
957 pj_memcpy(&ice_cfg.turn.cfg.qos_params, &cfg->qos_params,
958 sizeof(cfg->qos_params));
Benny Prijonof76e1392008-06-06 14:51:48 +0000959 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000960
Benny Prijono0bc99a92011-03-17 04:34:43 +0000961 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
962 ice_cb.on_ice_complete = &on_ice_complete;
963 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", call_med->idx);
964 call_med->tp_ready = PJ_EPENDING;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000965
Benny Prijono0bc99a92011-03-17 04:34:43 +0000966 comp_cnt = 1;
967 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
968 ++comp_cnt;
Benny Prijonof76e1392008-06-06 14:51:48 +0000969
Benny Prijonobd6613f2011-04-11 17:27:14 +0000970 status = pjmedia_ice_create3(pjsua_var.med_endpt, name, comp_cnt,
971 &ice_cfg, &ice_cb, 0, call_med,
972 &call_med->tp);
Benny Prijono0bc99a92011-03-17 04:34:43 +0000973 if (status != PJ_SUCCESS) {
974 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
975 status);
976 goto on_error;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000977 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000978
Benny Prijono0bc99a92011-03-17 04:34:43 +0000979 /* Wait until transport is initialized, or time out */
Sauw Ming73ecfe82011-09-21 10:20:01 +0000980 if (!async) {
Nanang Izzuddin1c565422011-12-28 09:52:07 +0000981 pj_bool_t has_pjsua_lock = PJSUA_LOCK_IS_LOCKED();
982 if (has_pjsua_lock)
983 PJSUA_UNLOCK();
Sauw Ming73ecfe82011-09-21 10:20:01 +0000984 while (call_med->tp_ready == PJ_EPENDING) {
985 pjsua_handle_events(100);
986 }
Nanang Izzuddin1c565422011-12-28 09:52:07 +0000987 if (has_pjsua_lock)
988 PJSUA_LOCK();
Benny Prijono0bc99a92011-03-17 04:34:43 +0000989 }
Sauw Ming73ecfe82011-09-21 10:20:01 +0000990
991 if (async && call_med->tp_ready == PJ_EPENDING) {
992 return PJ_EPENDING;
993 } else if (call_med->tp_ready != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000994 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
995 call_med->tp_ready);
996 status = call_med->tp_ready;
997 goto on_error;
998 }
999
1000 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
1001 pjsua_var.media_cfg.tx_drop_pct);
1002
1003 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
1004 pjsua_var.media_cfg.rx_drop_pct);
1005
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001006 return PJ_SUCCESS;
1007
1008on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +00001009 if (call_med->tp != NULL) {
1010 pjmedia_transport_close(call_med->tp);
1011 call_med->tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001012 }
1013
Benny Prijonoc97608e2007-03-23 16:34:20 +00001014 return status;
1015}
1016
Benny Prijono0bc99a92011-03-17 04:34:43 +00001017#if DISABLED_FOR_TICKET_1185
1018/* Create ICE media transports (when ice is enabled) */
1019static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
1020{
1021 unsigned i;
1022 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001023
Benny Prijono0bc99a92011-03-17 04:34:43 +00001024 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
1025 pjsua_call *call = &pjsua_var.calls[i];
1026 unsigned strm_idx;
1027
1028 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1029 pjsua_call_media *call_med = &call->media[strm_idx];
1030
1031 status = create_ice_media_transport(cfg, call_med);
1032 if (status != PJ_SUCCESS)
1033 goto on_error;
1034 }
1035 }
1036
1037 return PJ_SUCCESS;
1038
1039on_error:
1040 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
1041 pjsua_call *call = &pjsua_var.calls[i];
1042 unsigned strm_idx;
1043
1044 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1045 pjsua_call_media *call_med = &call->media[strm_idx];
1046
1047 if (call_med->tp) {
1048 pjmedia_transport_close(call_med->tp);
1049 call_med->tp = NULL;
1050 }
1051 }
1052 }
1053 return status;
1054}
1055#endif
1056
1057#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +00001058/*
Benny Prijono0bc99a92011-03-17 04:34:43 +00001059 * Create media transports for all the calls. This function creates
Benny Prijonoc97608e2007-03-23 16:34:20 +00001060 * one UDP media transport for each call.
1061 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001062PJ_DEF(pj_status_t) pjsua_media_transports_create(
1063 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001064{
1065 pjsua_transport_config cfg;
1066 unsigned i;
1067 pj_status_t status;
1068
1069
1070 /* Make sure pjsua_init() has been called */
1071 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
1072
1073 PJSUA_LOCK();
1074
1075 /* Delete existing media transports */
1076 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001077 pjsua_call *call = &pjsua_var.calls[i];
1078 unsigned strm_idx;
1079
1080 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1081 pjsua_call_media *call_med = &call->media[strm_idx];
1082
1083 if (call_med->tp && call_med->tp_auto_del) {
1084 pjmedia_transport_close(call_med->tp);
1085 call_med->tp = NULL;
1086 call_med->tp_orig = NULL;
1087 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001088 }
1089 }
1090
1091 /* Copy config */
1092 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
1093
Benny Prijono40860c32008-09-04 13:55:33 +00001094 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001095 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijono4d79b0f2009-10-25 09:02:07 +00001096 status = create_ice_media_transports(&cfg);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001097 } else {
1098 status = create_udp_media_transports(&cfg);
1099 }
1100
Benny Prijono40860c32008-09-04 13:55:33 +00001101 /* Set media transport auto_delete to True */
1102 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001103 pjsua_call *call = &pjsua_var.calls[i];
1104 unsigned strm_idx;
1105
1106 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1107 pjsua_call_media *call_med = &call->media[strm_idx];
1108
1109 call_med->tp_auto_del = PJ_TRUE;
1110 }
Benny Prijono40860c32008-09-04 13:55:33 +00001111 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001112
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001113 PJSUA_UNLOCK();
1114
1115 return status;
1116}
1117
Benny Prijono40860c32008-09-04 13:55:33 +00001118/*
1119 * Attach application's created media transports.
1120 */
1121PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
1122 unsigned count,
1123 pj_bool_t auto_delete)
1124{
1125 unsigned i;
1126
1127 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
1128
1129 /* Assign the media transports */
1130 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001131 pjsua_call *call = &pjsua_var.calls[i];
1132 unsigned strm_idx;
1133
1134 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1135 pjsua_call_media *call_med = &call->media[strm_idx];
1136
1137 if (call_med->tp && call_med->tp_auto_del) {
1138 pjmedia_transport_close(call_med->tp);
1139 call_med->tp = NULL;
1140 call_med->tp_orig = NULL;
1141 }
Benny Prijono40860c32008-09-04 13:55:33 +00001142 }
1143
Benny Prijono0bc99a92011-03-17 04:34:43 +00001144 PJ_TODO(remove_pjsua_media_transports_attach);
1145
1146 call->media[0].tp = tp[i].transport;
1147 call->media[0].tp_auto_del = auto_delete;
Benny Prijono40860c32008-09-04 13:55:33 +00001148 }
1149
1150 return PJ_SUCCESS;
1151}
Benny Prijono0bc99a92011-03-17 04:34:43 +00001152#endif
Benny Prijono40860c32008-09-04 13:55:33 +00001153
Benny Prijono0bc99a92011-03-17 04:34:43 +00001154/* Go through the list of media in the SDP, find acceptable media, and
1155 * sort them based on the "quality" of the media, and store the indexes
1156 * in the specified array. Media with the best quality will be listed
1157 * first in the array. The quality factors considered currently is
1158 * encryption.
1159 */
1160static void sort_media(const pjmedia_sdp_session *sdp,
1161 const pj_str_t *type,
1162 pjmedia_srtp_use use_srtp,
1163 pj_uint8_t midx[],
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001164 unsigned *p_count,
1165 unsigned *p_total_count)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001166{
1167 unsigned i;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001168 unsigned count = 0;
1169 int score[PJSUA_MAX_CALL_MEDIA];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001170
Benny Prijono0bc99a92011-03-17 04:34:43 +00001171 pj_assert(*p_count >= PJSUA_MAX_CALL_MEDIA);
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001172 pj_assert(*p_total_count >= PJSUA_MAX_CALL_MEDIA);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001173
1174 *p_count = 0;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001175 *p_total_count = 0;
Benny Prijono09c0d672011-04-11 05:03:24 +00001176 for (i=0; i<PJSUA_MAX_CALL_MEDIA; ++i)
1177 score[i] = 1;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001178
1179 /* Score each media */
1180 for (i=0; i<sdp->media_count && count<PJSUA_MAX_CALL_MEDIA; ++i) {
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001181 const pjmedia_sdp_media *m = sdp->media[i];
Nanang Izzuddina6414292011-04-08 04:26:18 +00001182 const pjmedia_sdp_conn *c;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001183
Benny Prijono0bc99a92011-03-17 04:34:43 +00001184 /* Skip different media */
1185 if (pj_stricmp(&m->desc.media, type) != 0) {
1186 score[count++] = -22000;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001187 continue;
1188 }
1189
Nanang Izzuddina6414292011-04-08 04:26:18 +00001190 c = m->conn? m->conn : sdp->conn;
1191
Benny Prijono0bc99a92011-03-17 04:34:43 +00001192 /* Supported transports */
1193 if (pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0) {
1194 switch (use_srtp) {
1195 case PJMEDIA_SRTP_MANDATORY:
1196 case PJMEDIA_SRTP_OPTIONAL:
1197 ++score[i];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001198 break;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001199 case PJMEDIA_SRTP_DISABLED:
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001200 //--score[i];
1201 score[i] -= 5;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001202 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001203 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001204 } else if (pj_stricmp2(&m->desc.transport, "RTP/AVP")==0) {
1205 switch (use_srtp) {
1206 case PJMEDIA_SRTP_MANDATORY:
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001207 //--score[i];
1208 score[i] -= 5;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001209 break;
1210 case PJMEDIA_SRTP_OPTIONAL:
1211 /* No change in score */
1212 break;
1213 case PJMEDIA_SRTP_DISABLED:
1214 ++score[i];
1215 break;
1216 }
1217 } else {
1218 score[i] -= 10;
1219 }
1220
1221 /* Is media disabled? */
1222 if (m->desc.port == 0)
1223 score[i] -= 10;
1224
1225 /* Is media inactive? */
Nanang Izzuddina6414292011-04-08 04:26:18 +00001226 if (pjmedia_sdp_media_find_attr2(m, "inactive", NULL) ||
1227 pj_strcmp2(&c->addr, "0.0.0.0") == 0)
1228 {
1229 //score[i] -= 10;
1230 score[i] -= 1;
1231 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001232
1233 ++count;
1234 }
1235
1236 /* Created sorted list based on quality */
1237 for (i=0; i<count; ++i) {
1238 unsigned j;
1239 int best = 0;
1240
1241 for (j=1; j<count; ++j) {
1242 if (score[j] > score[best])
1243 best = j;
1244 }
1245 /* Don't put media with negative score, that media is unacceptable
1246 * for us.
1247 */
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001248 midx[i] = (pj_uint8_t)best;
1249 if (score[best] >= 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00001250 (*p_count)++;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001251 if (score[best] > -22000)
1252 (*p_total_count)++;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001253
1254 score[best] = -22000;
1255
1256 }
1257}
1258
Benny Prijonoee0ba182011-07-15 06:18:29 +00001259/* Callback to receive media events */
Sauw Ming0fabe1b2011-12-01 10:49:07 +00001260pj_status_t call_media_on_event(pjmedia_event *event,
1261 void *user_data)
Benny Prijonoee0ba182011-07-15 06:18:29 +00001262{
Sauw Ming0fabe1b2011-12-01 10:49:07 +00001263 pjsua_call_media *call_med = (pjsua_call_media*)user_data;
Benny Prijonoee0ba182011-07-15 06:18:29 +00001264 pjsua_call *call = call_med->call;
Nanang Izzuddin3da4ad82011-12-07 10:43:28 +00001265 pj_status_t status = PJ_SUCCESS;
1266
1267 switch(event->type) {
1268 case PJMEDIA_EVENT_KEYFRAME_MISSING:
1269 if (call->opt.req_keyframe_method & PJSUA_VID_REQ_KEYFRAME_SIP_INFO)
1270 {
1271 pj_timestamp now;
1272
1273 pj_get_timestamp(&now);
1274 if (pj_elapsed_msec(&call_med->last_req_keyframe, &now) >=
1275 PJSUA_VID_REQ_KEYFRAME_INTERVAL)
1276 {
1277 pjsua_msg_data msg_data;
1278 const pj_str_t SIP_INFO = {"INFO", 4};
1279 const char *BODY_TYPE = "application/media_control+xml";
1280 const char *BODY =
1281 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
1282 "<media_control><vc_primitive><to_encoder>"
1283 "<picture_fast_update/>"
1284 "</to_encoder></vc_primitive></media_control>";
1285
1286 PJ_LOG(4,(THIS_FILE,
1287 "Sending video keyframe request via SIP INFO"));
1288
1289 pjsua_msg_data_init(&msg_data);
1290 pj_cstr(&msg_data.content_type, BODY_TYPE);
1291 pj_cstr(&msg_data.msg_body, BODY);
1292 status = pjsua_call_send_request(call->index, &SIP_INFO,
1293 &msg_data);
1294 if (status != PJ_SUCCESS) {
1295 pj_perror(3, THIS_FILE, status,
1296 "Failed requesting keyframe via SIP INFO");
1297 } else {
1298 call_med->last_req_keyframe = now;
1299 }
1300 }
1301 }
1302 break;
1303
1304 default:
1305 break;
1306 }
Benny Prijonoee0ba182011-07-15 06:18:29 +00001307
1308 if (pjsua_var.ua_cfg.cb.on_call_media_event && call) {
Benny Prijonoee0ba182011-07-15 06:18:29 +00001309 (*pjsua_var.ua_cfg.cb.on_call_media_event)(call->index,
1310 call_med->idx, event);
Benny Prijono53a7c702008-04-14 02:57:29 +00001311 }
1312
Nanang Izzuddin3da4ad82011-12-07 10:43:28 +00001313 return status;
Benny Prijono53a7c702008-04-14 02:57:29 +00001314}
1315
Sauw Ming73ecfe82011-09-21 10:20:01 +00001316/* Set media transport state and notify the application via the callback. */
1317void set_media_tp_state(pjsua_call_media *call_med,
1318 pjsua_med_tp_st tp_st)
1319{
1320 if (pjsua_var.ua_cfg.cb.on_call_media_transport_state &&
1321 call_med->tp_st != tp_st)
1322 {
1323 pjsua_med_tp_state_info info;
1324
1325 pj_bzero(&info, sizeof(info));
1326 info.med_idx = call_med->idx;
1327 info.state = tp_st;
1328 info.status = call_med->tp_ready;
1329 (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)(
1330 call_med->call->index, &info);
1331 }
1332
1333 call_med->tp_st = tp_st;
1334}
1335
1336/* Callback to resume pjsua_call_media_init() after media transport
1337 * creation is completed.
1338 */
1339static pj_status_t call_media_init_cb(pjsua_call_media *call_med,
1340 pj_status_t status,
1341 int security_level,
1342 int *sip_err_code)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001343{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001344 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
Benny Prijonodb5d89d2011-10-25 13:39:06 +00001345 pjmedia_transport_info tpinfo;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001346 int err_code = 0;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001347
Sauw Ming73ecfe82011-09-21 10:20:01 +00001348 if (status != PJ_SUCCESS)
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001349 goto on_return;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001350
Sauw Ming73ecfe82011-09-21 10:20:01 +00001351 if (call_med->tp_st == PJSUA_MED_TP_CREATING)
1352 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001353
Sauw Minge7dbbc82011-10-24 09:28:13 +00001354 if (!call_med->tp_orig &&
1355 pjsua_var.ua_cfg.cb.on_create_media_transport)
1356 {
1357 call_med->use_custom_med_tp = PJ_TRUE;
1358 } else
1359 call_med->use_custom_med_tp = PJ_FALSE;
1360
Benny Prijono0bc99a92011-03-17 04:34:43 +00001361#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1362 /* This function may be called when SRTP transport already exists
1363 * (e.g: in re-invite, update), don't need to destroy/re-create.
1364 */
Sauw Minge7dbbc82011-10-24 09:28:13 +00001365 if (!call_med->tp_orig) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001366 pjmedia_srtp_setting srtp_opt;
1367 pjmedia_transport *srtp = NULL;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001368
Benny Prijono0bc99a92011-03-17 04:34:43 +00001369 /* Check if SRTP requires secure signaling */
1370 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
1371 if (security_level < acc->cfg.srtp_secure_signaling) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001372 err_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001373 status = PJSIP_ESESSIONINSECURE;
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001374 goto on_return;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001375 }
1376 }
1377
1378 /* Always create SRTP adapter */
1379 pjmedia_srtp_setting_default(&srtp_opt);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001380 srtp_opt.close_member_tp = PJ_TRUE;
Sauw Minge7dbbc82011-10-24 09:28:13 +00001381 /* If media session has been ever established, let's use remote's
Benny Prijono0bc99a92011-03-17 04:34:43 +00001382 * preference in SRTP usage policy, especially when it is stricter.
1383 */
1384 if (call_med->rem_srtp_use > acc->cfg.use_srtp)
1385 srtp_opt.use = call_med->rem_srtp_use;
1386 else
1387 srtp_opt.use = acc->cfg.use_srtp;
1388
1389 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
1390 call_med->tp,
1391 &srtp_opt, &srtp);
1392 if (status != PJ_SUCCESS) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001393 err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001394 goto on_return;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001395 }
1396
1397 /* Set SRTP as current media transport */
1398 call_med->tp_orig = call_med->tp;
1399 call_med->tp = srtp;
1400 }
1401#else
Benny Prijono7df19342011-07-23 02:54:03 +00001402 call_med->tp_orig = call_med->tp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001403 PJ_UNUSED_ARG(security_level);
1404#endif
1405
Benny Prijonodb5d89d2011-10-25 13:39:06 +00001406
1407 pjmedia_transport_info_init(&tpinfo);
1408 pjmedia_transport_get_info(call_med->tp, &tpinfo);
1409
1410 pj_sockaddr_cp(&call_med->rtp_addr, &tpinfo.sock_info.rtp_addr_name);
1411
1412
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001413on_return:
Sauw Ming73ecfe82011-09-21 10:20:01 +00001414 if (status != PJ_SUCCESS && call_med->tp) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001415 pjmedia_transport_close(call_med->tp);
1416 call_med->tp = NULL;
1417 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001418
1419 if (sip_err_code)
1420 *sip_err_code = err_code;
1421
1422 if (call_med->med_init_cb) {
1423 pjsua_med_tp_state_info info;
1424
1425 pj_bzero(&info, sizeof(info));
1426 info.status = status;
1427 info.state = call_med->tp_st;
1428 info.med_idx = call_med->idx;
1429 info.sip_err_code = err_code;
1430 (*call_med->med_init_cb)(call_med->call->index, &info);
1431 }
1432
1433 return status;
1434}
1435
1436/* Initialize the media line */
1437pj_status_t pjsua_call_media_init(pjsua_call_media *call_med,
1438 pjmedia_type type,
1439 const pjsua_transport_config *tcfg,
1440 int security_level,
1441 int *sip_err_code,
1442 pj_bool_t async,
1443 pjsua_med_tp_state_cb cb)
1444{
Sauw Ming73ecfe82011-09-21 10:20:01 +00001445 pj_status_t status = PJ_SUCCESS;
1446
1447 /*
1448 * Note: this function may be called when the media already exists
1449 * (e.g. in reinvites, updates, etc.)
1450 */
1451 call_med->type = type;
1452
1453 /* Create the media transport for initial call. */
1454 if (call_med->tp == NULL) {
1455#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Nanang Izzuddin3d7385c2011-12-01 10:02:54 +00001456 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
1457
Sauw Ming73ecfe82011-09-21 10:20:01 +00001458 /* While in initial call, set default video devices */
1459 if (type == PJMEDIA_TYPE_VIDEO) {
1460 call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev;
1461 call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev;
1462 if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) {
1463 pjmedia_vid_dev_info info;
1464 pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info);
1465 call_med->strm.v.rdr_dev = info.id;
1466 }
1467 if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
1468 pjmedia_vid_dev_info info;
1469 pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info);
1470 call_med->strm.v.cap_dev = info.id;
1471 }
1472 }
1473#endif
1474
1475 set_media_tp_state(call_med, PJSUA_MED_TP_CREATING);
1476
Sauw Ming73ecfe82011-09-21 10:20:01 +00001477 if (pjsua_var.media_cfg.enable_ice) {
1478 status = create_ice_media_transport(tcfg, call_med, async);
Sauw Mingc8e12942011-10-25 08:51:02 +00001479 if (async && status == PJ_EPENDING) {
Sauw Ming848742f2011-09-28 04:20:30 +00001480 /* We will resume call media initialization in the
1481 * on_ice_complete() callback.
1482 */
Sauw Mingc8e12942011-10-25 08:51:02 +00001483 call_med->med_create_cb = &call_media_init_cb;
1484 call_med->med_init_cb = cb;
1485
Sauw Ming848742f2011-09-28 04:20:30 +00001486 return PJ_EPENDING;
1487 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001488 } else {
1489 status = create_udp_media_transport(tcfg, call_med);
1490 }
1491
Sauw Ming848742f2011-09-28 04:20:30 +00001492 if (status != PJ_SUCCESS) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001493 PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport"));
1494 return status;
1495 }
1496
1497 /* Media transport creation completed immediately, so
1498 * we don't need to call the callback.
1499 */
1500 call_med->med_init_cb = NULL;
1501
1502 } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) {
1503 /* Media is being reenabled. */
1504 set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
1505 }
1506
1507 return call_media_init_cb(call_med, status, security_level,
1508 sip_err_code);
1509}
1510
1511/* Callback to resume pjsua_media_channel_init() after media transport
1512 * initialization is completed.
1513 */
1514static pj_status_t media_channel_init_cb(pjsua_call_id call_id,
1515 const pjsua_med_tp_state_info *info)
1516{
1517 pjsua_call *call = &pjsua_var.calls[call_id];
1518 pj_status_t status = (info? info->status : PJ_SUCCESS);
1519 unsigned mi;
1520
1521 if (info) {
1522 pj_mutex_lock(call->med_ch_mutex);
1523
1524 /* Set the callback to NULL to indicate that the async operation
1525 * has completed.
1526 */
1527 call->media[info->med_idx].med_init_cb = NULL;
1528
1529 /* In case of failure, save the information to be returned
1530 * by the last media transport to finish.
1531 */
1532 if (info->status != PJ_SUCCESS)
1533 pj_memcpy(&call->med_ch_info, info, sizeof(info));
1534
1535 /* Check whether all the call's medias have finished calling their
1536 * callbacks.
1537 */
1538 for (mi=0; mi < call->med_cnt; ++mi) {
1539 pjsua_call_media *call_med = &call->media[mi];
1540
Sauw Ming3a55bb92011-10-06 06:49:09 +00001541 if (call_med->med_init_cb ||
1542 call_med->tp_st == PJSUA_MED_TP_NULL)
1543 {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001544 pj_mutex_unlock(call->med_ch_mutex);
1545 return PJ_SUCCESS;
1546 }
1547
1548 if (call_med->tp_ready != PJ_SUCCESS)
1549 status = call_med->tp_ready;
1550 }
1551
1552 /* OK, we are called by the last media transport finished. */
1553 pj_mutex_unlock(call->med_ch_mutex);
1554 }
1555
1556 if (call->med_ch_mutex) {
1557 pj_mutex_destroy(call->med_ch_mutex);
1558 call->med_ch_mutex = NULL;
1559 }
1560
1561 if (status != PJ_SUCCESS) {
1562 pjsua_media_channel_deinit(call_id);
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001563 goto on_return;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001564 }
1565
1566 /* Tell the media transport of a new offer/answer session */
1567 for (mi=0; mi < call->med_cnt; ++mi) {
1568 pjsua_call_media *call_med = &call->media[mi];
1569
1570 /* Note: tp may be NULL if this media line is disabled */
1571 if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) {
Sauw Ming99cc8ff2011-09-22 04:24:56 +00001572 pj_pool_t *tmp_pool = call->async_call.pool_prov;
1573
1574 if (!tmp_pool) {
1575 tmp_pool = (call->inv? call->inv->pool_prov:
1576 call->async_call.dlg->pool);
1577 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001578
Sauw Minge7dbbc82011-10-24 09:28:13 +00001579 if (call_med->use_custom_med_tp) {
1580 unsigned custom_med_tp_flags = 0;
1581
1582 /* Use custom media transport returned by the application */
1583 call_med->tp =
1584 (*pjsua_var.ua_cfg.cb.on_create_media_transport)
1585 (call_id, mi, call_med->tp,
1586 custom_med_tp_flags);
1587 if (!call_med->tp) {
1588 status =
1589 PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
1590 }
1591 }
1592
1593 if (call_med->tp) {
1594 status = pjmedia_transport_media_create(
1595 call_med->tp, tmp_pool,
1596 0, call->async_call.rem_sdp, mi);
1597 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001598 if (status != PJ_SUCCESS) {
1599 call->med_ch_info.status = status;
1600 call->med_ch_info.med_idx = mi;
1601 call->med_ch_info.state = call_med->tp_st;
1602 call->med_ch_info.sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1603 pjsua_media_channel_deinit(call_id);
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001604 goto on_return;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001605 }
1606
1607 set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
1608 }
1609 }
1610
1611 call->med_ch_info.status = PJ_SUCCESS;
1612
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001613on_return:
Sauw Ming73ecfe82011-09-21 10:20:01 +00001614 if (call->med_ch_cb)
1615 (*call->med_ch_cb)(call->index, &call->med_ch_info);
1616
Benny Prijono0bc99a92011-03-17 04:34:43 +00001617 return status;
1618}
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001619
Benny Prijonod8179652008-01-23 20:39:07 +00001620pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
1621 pjsip_role_e role,
1622 int security_level,
1623 pj_pool_t *tmp_pool,
1624 const pjmedia_sdp_session *rem_sdp,
Sauw Ming73ecfe82011-09-21 10:20:01 +00001625 int *sip_err_code,
1626 pj_bool_t async,
1627 pjsua_med_tp_state_cb cb)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001628{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001629 const pj_str_t STR_AUDIO = { "audio", 5 };
1630 const pj_str_t STR_VIDEO = { "video", 5 };
Benny Prijonod8179652008-01-23 20:39:07 +00001631 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijonod8179652008-01-23 20:39:07 +00001632 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001633 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1634 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001635 unsigned mtotaudcnt = PJ_ARRAY_SIZE(maudidx);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001636 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
1637 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001638 unsigned mtotvidcnt = PJ_ARRAY_SIZE(mvididx);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001639 unsigned mi;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001640 pj_bool_t pending_med_tp = PJ_FALSE;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001641 pj_bool_t reinit = PJ_FALSE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001642 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001643
Benny Prijonod8179652008-01-23 20:39:07 +00001644 PJ_UNUSED_ARG(role);
1645
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001646 /*
1647 * Note: this function may be called when the media already exists
1648 * (e.g. in reinvites, updates, etc).
1649 */
1650
Benny Prijono0bc99a92011-03-17 04:34:43 +00001651 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
1652 return PJ_EBUSY;
1653
Sauw Ming73ecfe82011-09-21 10:20:01 +00001654 if (async) {
1655 pj_pool_t *tmppool = (call->inv? call->inv->pool_prov:
1656 call->async_call.dlg->pool);
1657
1658 status = pj_mutex_create_simple(tmppool, NULL, &call->med_ch_mutex);
1659 if (status != PJ_SUCCESS)
1660 return status;
1661 }
1662
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001663 if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED)
1664 reinit = PJ_TRUE;
1665
1666 PJ_LOG(4,(THIS_FILE, "Call %d: %sinitializing media..",
1667 call_id, (reinit?"re-":"") ));
1668
Benny Prijonob90fd382011-09-18 14:59:56 +00001669 pj_log_push_indent();
1670
Benny Prijono0bc99a92011-03-17 04:34:43 +00001671#if DISABLED_FOR_TICKET_1185
Benny Prijonod8179652008-01-23 20:39:07 +00001672 /* Return error if media transport has not been created yet
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001673 * (e.g. application is starting)
1674 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001675 for (i=0; i<call->med_cnt; ++i) {
1676 if (call->media[i].tp == NULL) {
Benny Prijonob90fd382011-09-18 14:59:56 +00001677 status = PJ_EBUSY;
1678 goto on_error;
Benny Prijonod8179652008-01-23 20:39:07 +00001679 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001680 }
Benny Prijonod8179652008-01-23 20:39:07 +00001681#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +00001682
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001683 /* Get media count for each media type */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001684 if (rem_sdp) {
1685 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001686 maudidx, &maudcnt, &mtotaudcnt);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001687 if (maudcnt==0) {
1688 /* Expecting audio in the offer */
1689 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1690 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001691 status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
1692 goto on_error;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001693 }
1694
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001695#if PJMEDIA_HAS_VIDEO
Benny Prijono0bc99a92011-03-17 04:34:43 +00001696 sort_media(rem_sdp, &STR_VIDEO, acc->cfg.use_srtp,
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001697 mvididx, &mvidcnt, &mtotvidcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001698#else
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001699 mvidcnt = mtotvidcnt = 0;
1700 PJ_UNUSED_ARG(STR_VIDEO);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001701#endif
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001702
1703 /* Update media count only when remote add any media, this media count
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001704 * must never decrease. Also note that we shouldn't apply the media
1705 * count setting (of the call setting) before the SDP negotiation.
Benny Prijonod8179652008-01-23 20:39:07 +00001706 */
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001707 if (call->med_cnt < rem_sdp->media_count)
1708 call->med_cnt = PJ_MIN(rem_sdp->media_count, PJSUA_MAX_CALL_MEDIA);
Benny Prijonod8179652008-01-23 20:39:07 +00001709
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001710 call->rem_offerer = PJ_TRUE;
1711 call->rem_aud_cnt = maudcnt;
1712 call->rem_vid_cnt = mvidcnt;
1713
Benny Prijono0bc99a92011-03-17 04:34:43 +00001714 } else {
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001715
1716 /* If call already established, calculate media count from current
1717 * local active SDP and call setting. Otherwise, calculate media
1718 * count from the call setting only.
1719 */
1720 if (reinit) {
Benny Prijono0ec6cfb2011-12-26 09:00:42 +00001721 const pjmedia_sdp_session *sdp;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001722
1723 status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &sdp);
1724 pj_assert(status == PJ_SUCCESS);
1725
1726 sort_media(sdp, &STR_AUDIO, acc->cfg.use_srtp,
1727 maudidx, &maudcnt, &mtotaudcnt);
1728 pj_assert(maudcnt > 0);
1729
1730 sort_media(sdp, &STR_VIDEO, acc->cfg.use_srtp,
1731 mvididx, &mvidcnt, &mtotvidcnt);
1732
1733 /* Call setting may add or remove media. Adding media is done by
1734 * enabling any disabled/port-zeroed media first, then adding new
1735 * media whenever needed. Removing media is done by disabling
1736 * media with the lowest 'quality'.
1737 */
1738
1739 /* Check if we need to add new audio */
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001740 if (maudcnt < call->opt.aud_cnt &&
1741 mtotaudcnt < call->opt.aud_cnt)
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001742 {
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001743 for (mi = 0; mi < call->opt.aud_cnt - mtotaudcnt; ++mi)
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001744 maudidx[maudcnt++] = (pj_uint8_t)call->med_cnt++;
1745
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001746 mtotaudcnt = call->opt.aud_cnt;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001747 }
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001748 maudcnt = call->opt.aud_cnt;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001749
1750 /* Check if we need to add new video */
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001751 if (mvidcnt < call->opt.vid_cnt &&
1752 mtotvidcnt < call->opt.vid_cnt)
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001753 {
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001754 for (mi = 0; mi < call->opt.vid_cnt - mtotvidcnt; ++mi)
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001755 mvididx[mvidcnt++] = (pj_uint8_t)call->med_cnt++;
1756
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001757 mtotvidcnt = call->opt.vid_cnt;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001758 }
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001759 mvidcnt = call->opt.vid_cnt;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001760
1761 } else {
1762
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001763 maudcnt = mtotaudcnt = call->opt.aud_cnt;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001764 for (mi=0; mi<maudcnt; ++mi) {
1765 maudidx[mi] = (pj_uint8_t)mi;
1766 }
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001767 mvidcnt = mtotvidcnt = call->opt.vid_cnt;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001768 for (mi=0; mi<mvidcnt; ++mi) {
1769 mvididx[mi] = (pj_uint8_t)(maudcnt + mi);
1770 }
1771 call->med_cnt = maudcnt + mvidcnt;
1772
1773 /* Need to publish supported media? */
1774 if (call->opt.flag & PJSUA_CALL_INCLUDE_DISABLED_MEDIA) {
1775 if (mtotaudcnt == 0) {
1776 mtotaudcnt = 1;
1777 maudidx[0] = (pj_uint8_t)call->med_cnt++;
1778 }
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001779#if PJMEDIA_HAS_VIDEO
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001780 if (mtotvidcnt == 0) {
1781 mtotvidcnt = 1;
1782 mvididx[0] = (pj_uint8_t)call->med_cnt++;
1783 }
1784#endif
1785 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001786 }
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001787
1788 call->rem_offerer = PJ_FALSE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001789 }
1790
Benny Prijono0bc99a92011-03-17 04:34:43 +00001791 if (call->med_cnt == 0) {
1792 /* Expecting at least one media */
Benny Prijonoab8dba92008-06-27 21:59:15 +00001793 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001794 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001795 status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
1796 goto on_error;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001797 }
1798
Sauw Ming73ecfe82011-09-21 10:20:01 +00001799 if (async) {
1800 call->med_ch_cb = cb;
Benny Prijono7eff5ef2011-10-26 06:53:30 +00001801 }
1802
1803 if (rem_sdp) {
1804 call->async_call.rem_sdp =
1805 pjmedia_sdp_session_clone(call->inv->pool_prov, rem_sdp);
1806 } else {
1807 call->async_call.rem_sdp = NULL;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001808 }
1809
Sauw Ming99cc8ff2011-09-22 04:24:56 +00001810 call->async_call.pool_prov = tmp_pool;
1811
Benny Prijono0bc99a92011-03-17 04:34:43 +00001812 /* Initialize each media line */
1813 for (mi=0; mi < call->med_cnt; ++mi) {
1814 pjsua_call_media *call_med = &call->media[mi];
1815 pj_bool_t enabled = PJ_FALSE;
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001816 pjmedia_type media_type = PJMEDIA_TYPE_UNKNOWN;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001817
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001818 if (pj_memchr(maudidx, mi, mtotaudcnt * sizeof(maudidx[0]))) {
1819 media_type = PJMEDIA_TYPE_AUDIO;
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001820 if (call->opt.aud_cnt &&
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001821 pj_memchr(maudidx, mi, maudcnt * sizeof(maudidx[0])))
1822 {
1823 enabled = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001824 }
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001825 } else if (pj_memchr(mvididx, mi, mtotvidcnt * sizeof(mvididx[0]))) {
1826 media_type = PJMEDIA_TYPE_VIDEO;
Benny Prijonoae86e6a2011-12-27 12:47:52 +00001827 if (call->opt.vid_cnt &&
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001828 pj_memchr(mvididx, mi, mvidcnt * sizeof(mvididx[0])))
1829 {
1830 enabled = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001831 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001832 }
1833
1834 if (enabled) {
1835 status = pjsua_call_media_init(call_med, media_type,
1836 &acc->cfg.rtp_cfg,
Sauw Ming73ecfe82011-09-21 10:20:01 +00001837 security_level, sip_err_code,
1838 async,
Sauw Ming3a55bb92011-10-06 06:49:09 +00001839 (async? &media_channel_init_cb:
1840 NULL));
Sauw Ming73ecfe82011-09-21 10:20:01 +00001841 if (status == PJ_EPENDING) {
1842 pending_med_tp = PJ_TRUE;
1843 } else if (status != PJ_SUCCESS) {
1844 if (pending_med_tp) {
1845 /* Save failure information. */
1846 call_med->tp_ready = status;
1847 pj_bzero(&call->med_ch_info, sizeof(call->med_ch_info));
1848 call->med_ch_info.status = status;
1849 call->med_ch_info.state = call_med->tp_st;
1850 call->med_ch_info.med_idx = call_med->idx;
1851 if (sip_err_code)
1852 call->med_ch_info.sip_err_code = *sip_err_code;
1853
1854 /* We will return failure in the callback later. */
1855 return PJ_EPENDING;
1856 }
1857
1858 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001859 goto on_error;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001860 }
1861 } else {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001862 /* By convention, the media is disabled if transport is NULL
1863 * or transport state is PJSUA_MED_TP_DISABLED.
1864 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001865 if (call_med->tp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001866 // Don't close transport here, as SDP negotiation has not been
1867 // done and stream may be still active.
1868 //pjmedia_transport_close(call_med->tp);
1869 //call_med->tp = NULL;
1870 pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT ||
1871 call_med->tp_st == PJSUA_MED_TP_RUNNING);
Sauw Ming73ecfe82011-09-21 10:20:01 +00001872 set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001873 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001874
1875 /* Put media type just for info */
1876 call_med->type = media_type;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001877 }
Benny Prijono224b4e22008-06-19 14:10:28 +00001878 }
1879
Benny Prijono0bc99a92011-03-17 04:34:43 +00001880 call->audio_idx = maudidx[0];
1881
1882 PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d",
1883 call->audio_idx, call->index));
1884
Sauw Ming73ecfe82011-09-21 10:20:01 +00001885 if (pending_med_tp) {
Sauw Ming99cc8ff2011-09-22 04:24:56 +00001886 /* We shouldn't use temporary pool anymore. */
1887 call->async_call.pool_prov = NULL;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001888 /* We have a pending media transport initialization. */
1889 pj_log_pop_indent();
1890 return PJ_EPENDING;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001891 }
1892
Sauw Ming73ecfe82011-09-21 10:20:01 +00001893 /* Media transport initialization completed immediately, so
1894 * we don't need to call the callback.
1895 */
1896 call->med_ch_cb = NULL;
1897
1898 status = media_channel_init_cb(call_id, NULL);
1899 if (status != PJ_SUCCESS && sip_err_code)
1900 *sip_err_code = call->med_ch_info.sip_err_code;
1901
Benny Prijonob90fd382011-09-18 14:59:56 +00001902 pj_log_pop_indent();
Sauw Ming73ecfe82011-09-21 10:20:01 +00001903 return status;
Benny Prijonob90fd382011-09-18 14:59:56 +00001904
1905on_error:
Sauw Ming73ecfe82011-09-21 10:20:01 +00001906 if (call->med_ch_mutex) {
1907 pj_mutex_destroy(call->med_ch_mutex);
1908 call->med_ch_mutex = NULL;
1909 }
1910
Benny Prijonob90fd382011-09-18 14:59:56 +00001911 pj_log_pop_indent();
1912 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001913}
1914
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001915
1916/* Create SDP based on the current media channel. Note that, this function
1917 * will not modify the media channel, so when receiving new offer or
1918 * updating media count (via call setting), media channel must be reinit'd
1919 * (using pjsua_media_channel_init()) first before calling this function.
1920 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001921pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1922 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001923 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001924 pjmedia_sdp_session **p_sdp,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001925 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001926{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001927 enum { MAX_MEDIA = PJSUA_MAX_CALL_MEDIA };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001928 pjmedia_sdp_session *sdp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001929 pj_sockaddr origin;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001930 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001931 pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001932 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001933 pj_status_t status;
1934
Benny Prijono0bc99a92011-03-17 04:34:43 +00001935 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
Benny Prijono55e82352007-05-10 20:49:08 +00001936 return PJ_EBUSY;
Benny Prijono55e82352007-05-10 20:49:08 +00001937
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001938#if 0
1939 // This function should not really change the media channel.
Benny Prijono0bc99a92011-03-17 04:34:43 +00001940 if (rem_sdp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001941 /* If this is a re-offer, let's re-initialize media as remote may
1942 * add or remove media
1943 */
1944 if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) {
1945 status = pjsua_media_channel_init(call_id, PJSIP_ROLE_UAS,
1946 call->secure_level, pool,
Sauw Ming73ecfe82011-09-21 10:20:01 +00001947 rem_sdp, sip_err_code,
1948 PJ_FALSE, NULL);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001949 if (status != PJ_SUCCESS)
1950 return status;
Benny Prijono0324ba52010-12-02 10:41:46 +00001951 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001952 } else {
1953 /* Audio is first in our offer, by convention */
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001954 // The audio_idx should not be changed here, as this function may be
1955 // called in generating re-offer and the current active audio index
1956 // can be anywhere.
1957 //call->audio_idx = 0;
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001958 }
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00001959#endif
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001960
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001961#if 0
1962 // Since r3512, old-style hold should have got transport, created by
1963 // pjsua_media_channel_init() in initial offer/answer or remote reoffer.
Benny Prijono224b4e22008-06-19 14:10:28 +00001964 /* Create media if it's not created. This could happen when call is
Benny Prijono0bc99a92011-03-17 04:34:43 +00001965 * currently on-hold (with the old style hold)
Benny Prijono224b4e22008-06-19 14:10:28 +00001966 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001967 if (call->media[call->audio_idx].tp == NULL) {
Benny Prijono224b4e22008-06-19 14:10:28 +00001968 pjsip_role_e role;
1969 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1970 status = pjsua_media_channel_init(call_id, role, call->secure_level,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001971 pool, rem_sdp, sip_err_code);
Benny Prijono224b4e22008-06-19 14:10:28 +00001972 if (status != PJ_SUCCESS)
1973 return status;
1974 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001975#endif
Benny Prijono224b4e22008-06-19 14:10:28 +00001976
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001977 /* Get SDP negotiator state */
1978 if (call->inv && call->inv->neg)
1979 sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg);
1980
Benny Prijono0bc99a92011-03-17 04:34:43 +00001981 /* Get one address to use in the origin field */
1982 pj_bzero(&origin, sizeof(origin));
1983 for (mi=0; mi<call->med_cnt; ++mi) {
1984 pjmedia_transport_info tpinfo;
Benny Prijono617c5bc2007-04-02 19:51:21 +00001985
Benny Prijono0bc99a92011-03-17 04:34:43 +00001986 if (call->media[mi].tp == NULL)
1987 continue;
1988
1989 pjmedia_transport_info_init(&tpinfo);
1990 pjmedia_transport_get_info(call->media[mi].tp, &tpinfo);
1991 pj_sockaddr_cp(&origin, &tpinfo.sock_info.rtp_addr_name);
1992 break;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001993 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001994
Benny Prijono0bc99a92011-03-17 04:34:43 +00001995 /* Create the base (blank) SDP */
1996 status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, pool, NULL,
1997 &origin, &sdp);
1998 if (status != PJ_SUCCESS)
1999 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00002000
Benny Prijono0bc99a92011-03-17 04:34:43 +00002001 /* Process each media line */
2002 for (mi=0; mi<call->med_cnt; ++mi) {
2003 pjsua_call_media *call_med = &call->media[mi];
2004 pjmedia_sdp_media *m = NULL;
2005 pjmedia_transport_info tpinfo;
2006
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002007 if (rem_sdp && mi >= rem_sdp->media_count) {
2008 /* Remote might have removed some media lines. */
2009 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002010 }
2011
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002012 if (call_med->tp == NULL || call_med->tp_st == PJSUA_MED_TP_DISABLED)
2013 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002014 /*
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002015 * This media is disabled. Just create a valid SDP with zero
Benny Prijono0bc99a92011-03-17 04:34:43 +00002016 * port.
2017 */
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002018 if (rem_sdp) {
2019 /* Just clone the remote media and deactivate it */
2020 m = pjmedia_sdp_media_clone_deactivate(pool,
2021 rem_sdp->media[mi]);
2022 } else {
2023 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
2024 m->desc.transport = pj_str("RTP/AVP");
2025 m->desc.fmt_count = 1;
2026 m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
2027 m->conn->net_type = pj_str("IN");
2028 m->conn->addr_type = pj_str("IP4");
2029 m->conn->addr = pj_str("127.0.0.1");
Benny Prijonoa310bd22008-06-27 21:19:44 +00002030
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002031 switch (call_med->type) {
2032 case PJMEDIA_TYPE_AUDIO:
2033 m->desc.media = pj_str("audio");
2034 m->desc.fmt[0] = pj_str("0");
2035 break;
2036 case PJMEDIA_TYPE_VIDEO:
2037 m->desc.media = pj_str("video");
2038 m->desc.fmt[0] = pj_str("31");
2039 break;
2040 default:
Nanang Izzuddincfa312c2011-10-26 16:57:05 +00002041 /* This must be us generating re-offer, and some unknown
2042 * media may exist, so just clone from active local SDP
2043 * (and it should have been deactivated already).
2044 */
2045 pj_assert(call->inv && call->inv->neg &&
2046 sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE);
2047 {
2048 const pjmedia_sdp_session *s_;
2049 pjmedia_sdp_neg_get_active_local(call->inv->neg, &s_);
2050
2051 pj_assert(mi < s_->media_count);
2052 m = pjmedia_sdp_media_clone(pool, s_->media[mi]);
2053 m->desc.port = 0;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002054 }
Nanang Izzuddincfa312c2011-10-26 16:57:05 +00002055 break;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002056 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00002057 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00002058
2059 sdp->media[sdp->media_count++] = m;
2060 continue;
Benny Prijonoa310bd22008-06-27 21:19:44 +00002061 }
2062
Benny Prijono0bc99a92011-03-17 04:34:43 +00002063 /* Get transport address info */
2064 pjmedia_transport_info_init(&tpinfo);
2065 pjmedia_transport_get_info(call_med->tp, &tpinfo);
Benny Prijonoa310bd22008-06-27 21:19:44 +00002066
Benny Prijono0bc99a92011-03-17 04:34:43 +00002067 /* Ask pjmedia endpoint to create SDP media line */
2068 switch (call_med->type) {
2069 case PJMEDIA_TYPE_AUDIO:
2070 status = pjmedia_endpt_create_audio_sdp(pjsua_var.med_endpt, pool,
2071 &tpinfo.sock_info, 0, &m);
2072 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00002073#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002074 case PJMEDIA_TYPE_VIDEO:
2075 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
2076 &tpinfo.sock_info, 0, &m);
2077 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00002078#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002079 default:
2080 pj_assert(!"Invalid call_med media type");
2081 return PJ_EBUG;
2082 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00002083
Benny Prijono0bc99a92011-03-17 04:34:43 +00002084 if (status != PJ_SUCCESS)
2085 return status;
2086
2087 sdp->media[sdp->media_count++] = m;
2088
2089 /* Give to transport */
2090 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
2091 sdp, rem_sdp, mi);
2092 if (status != PJ_SUCCESS) {
2093 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
2094 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00002095 }
Nanang Izzuddin91ba2a22011-04-11 19:59:09 +00002096
2097 /* Copy c= line of the first media to session level,
2098 * if there's none.
2099 */
2100 if (sdp->conn == NULL) {
2101 sdp->conn = pjmedia_sdp_conn_clone(pool, m->conn);
Benny Prijonoa310bd22008-06-27 21:19:44 +00002102 }
2103 }
2104
Benny Prijono6ba8c542007-10-16 01:34:14 +00002105 /* Add NAT info in the SDP */
2106 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
2107 pjmedia_sdp_attr *a;
2108 pj_str_t value;
2109 char nat_info[80];
2110
2111 value.ptr = nat_info;
2112 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
2113 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
2114 "%d", pjsua_var.nat_type);
2115 } else {
2116 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
2117 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
2118 "%d %s",
2119 pjsua_var.nat_type,
2120 type_name);
2121 }
2122
2123 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
2124
2125 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
2126
2127 }
2128
Benny Prijonoc97608e2007-03-23 16:34:20 +00002129
Benny Prijono0bc99a92011-03-17 04:34:43 +00002130#if DISABLED_FOR_TICKET_1185 && defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002131 /* Check if SRTP is in optional mode and configured to use duplicated
2132 * media, i.e: secured and unsecured version, in the SDP offer.
2133 */
2134 if (!rem_sdp &&
2135 pjsua_var.acc[call->acc_id].cfg.use_srtp == PJMEDIA_SRTP_OPTIONAL &&
2136 pjsua_var.acc[call->acc_id].cfg.srtp_optional_dup_offer)
2137 {
2138 unsigned i;
2139
2140 for (i = 0; i < sdp->media_count; ++i) {
2141 pjmedia_sdp_media *m = sdp->media[i];
2142
Benny Prijono0bc99a92011-03-17 04:34:43 +00002143 /* Check if this media is unsecured but has SDP "crypto"
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002144 * attribute.
2145 */
2146 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") == 0 &&
2147 pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL)
2148 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002149 if (i == (unsigned)call->audio_idx &&
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002150 sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE)
2151 {
2152 /* This is a session update, and peer has chosen the
2153 * unsecured version, so let's make this unsecured too.
2154 */
2155 pjmedia_sdp_media_remove_all_attr(m, "crypto");
2156 } else {
2157 /* This is new offer, duplicate media so we'll have
2158 * secured (with "RTP/SAVP" transport) and and unsecured
2159 * versions.
2160 */
2161 pjmedia_sdp_media *new_m;
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002162
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002163 /* Duplicate this media and apply secured transport */
2164 new_m = pjmedia_sdp_media_clone(pool, m);
2165 pj_strdup2(pool, &new_m->desc.transport, "RTP/SAVP");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002166
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002167 /* Remove the "crypto" attribute in the unsecured media */
2168 pjmedia_sdp_media_remove_all_attr(m, "crypto");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002169
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002170 /* Insert the new media before the unsecured media */
2171 if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002172 pj_array_insert(sdp->media, sizeof(new_m),
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002173 sdp->media_count, i, &new_m);
2174 ++sdp->media_count;
2175 ++i;
2176 }
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002177 }
2178 }
2179 }
2180 }
2181#endif
2182
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002183 call->rem_offerer = (rem_sdp != NULL);
2184
Benny Prijonoc97608e2007-03-23 16:34:20 +00002185 *p_sdp = sdp;
2186 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002187}
2188
2189
2190static void stop_media_session(pjsua_call_id call_id)
2191{
2192 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002193 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002194
Benny Prijonob90fd382011-09-18 14:59:56 +00002195 pj_log_push_indent();
2196
Benny Prijono0bc99a92011-03-17 04:34:43 +00002197 for (mi=0; mi<call->med_cnt; ++mi) {
2198 pjsua_call_media *call_med = &call->media[mi];
2199
2200 if (call_med->type == PJMEDIA_TYPE_AUDIO) {
2201 pjmedia_stream *strm = call_med->strm.a.stream;
2202 pjmedia_rtcp_stat stat;
2203
2204 if (strm) {
2205 if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) {
2206 if (pjsua_var.mconf) {
2207 pjsua_conf_remove_port(call_med->strm.a.conf_slot);
2208 }
2209 call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
2210 }
2211
2212 if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
2213 (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS))
2214 {
2215 /* Save RTP timestamp & sequence, so when media session is
2216 * restarted, those values will be restored as the initial
2217 * RTP timestamp & sequence of the new media session. So in
2218 * the same call session, RTP timestamp and sequence are
2219 * guaranteed to be contigue.
2220 */
2221 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
2222 call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
2223 call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
2224 }
2225
2226 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
2227 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, strm, mi);
2228 }
2229
2230 pjmedia_stream_destroy(strm);
2231 call_med->strm.a.stream = NULL;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002232 }
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00002233 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002234
2235#if PJMEDIA_HAS_VIDEO
2236 else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
Nanang Izzuddin62053a62011-07-12 11:08:32 +00002237 stop_video_stream(call_med);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002238 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002239#endif
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002240
2241 PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed",
2242 call_id, mi));
Sauw Minge7dbbc82011-10-24 09:28:13 +00002243 call_med->prev_state = call_med->state;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002244 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002245 }
Benny Prijonob90fd382011-09-18 14:59:56 +00002246
2247 pj_log_pop_indent();
Benny Prijonoc97608e2007-03-23 16:34:20 +00002248}
2249
2250pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
2251{
2252 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002253 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002254
Sauw Ming903154f2011-10-03 08:22:48 +00002255 PJSUA_LOCK();
Sauw Mingec765352011-10-03 02:04:36 +00002256 for (mi=0; mi<call->med_cnt; ++mi) {
2257 pjsua_call_media *call_med = &call->media[mi];
2258
Sauw Ming903154f2011-10-03 08:22:48 +00002259 if (call_med->tp_st == PJSUA_MED_TP_CREATING) {
2260 /* We will do the deinitialization after media transport
2261 * creation is completed.
2262 */
2263 call->async_call.med_ch_deinit = PJ_TRUE;
2264 PJSUA_UNLOCK();
2265 return PJ_SUCCESS;
2266 }
Sauw Mingec765352011-10-03 02:04:36 +00002267 }
Sauw Ming903154f2011-10-03 08:22:48 +00002268 PJSUA_UNLOCK();
Sauw Mingec765352011-10-03 02:04:36 +00002269
Benny Prijonob90fd382011-09-18 14:59:56 +00002270 PJ_LOG(4,(THIS_FILE, "Call %d: deinitializing media..", call_id));
2271 pj_log_push_indent();
2272
Sauw Minge7dbbc82011-10-24 09:28:13 +00002273 for (mi=0; mi<call->med_cnt; ++mi) {
2274 pjsua_call_media *call_med = &call->media[mi];
2275
2276 if (call_med->type == PJMEDIA_TYPE_AUDIO && call_med->strm.a.stream)
2277 pjmedia_stream_send_rtcp_bye(call_med->strm.a.stream);
2278 }
2279
Benny Prijonoc97608e2007-03-23 16:34:20 +00002280 stop_media_session(call_id);
2281
Benny Prijono0bc99a92011-03-17 04:34:43 +00002282 for (mi=0; mi<call->med_cnt; ++mi) {
2283 pjsua_call_media *call_med = &call->media[mi];
Benny Prijonoc97608e2007-03-23 16:34:20 +00002284
Sauw Minge7dbbc82011-10-24 09:28:13 +00002285 if (call_med->tp_st > PJSUA_MED_TP_IDLE) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002286 pjmedia_transport_media_stop(call_med->tp);
Sauw Ming73ecfe82011-09-21 10:20:01 +00002287 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002288 }
2289
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002290 //if (call_med->tp_orig && call_med->tp &&
2291 // call_med->tp != call_med->tp_orig)
2292 //{
2293 // pjmedia_transport_close(call_med->tp);
2294 // call_med->tp = call_med->tp_orig;
2295 //}
2296 if (call_med->tp) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002297 pjmedia_transport_close(call_med->tp);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002298 call_med->tp = call_med->tp_orig = NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002299 }
Sauw Minge7dbbc82011-10-24 09:28:13 +00002300 call_med->tp_orig = NULL;
Benny Prijonod8179652008-01-23 20:39:07 +00002301 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00002302
2303 check_snd_dev_idle();
Benny Prijonob90fd382011-09-18 14:59:56 +00002304 pj_log_pop_indent();
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00002305
Benny Prijonoc97608e2007-03-23 16:34:20 +00002306 return PJ_SUCCESS;
2307}
2308
2309
2310/*
2311 * DTMF callback from the stream.
2312 */
2313static void dtmf_callback(pjmedia_stream *strm, void *user_data,
2314 int digit)
2315{
2316 PJ_UNUSED_ARG(strm);
2317
Benny Prijonob90fd382011-09-18 14:59:56 +00002318 pj_log_push_indent();
2319
Benny Prijono0c068262008-02-14 14:38:52 +00002320 /* For discussions about call mutex protection related to this
2321 * callback, please see ticket #460:
2322 * http://trac.pjsip.org/repos/ticket/460#comment:4
2323 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00002324 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
2325 pjsua_call_id call_id;
2326
Benny Prijonod8179652008-01-23 20:39:07 +00002327 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002328 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
2329 }
Benny Prijonob90fd382011-09-18 14:59:56 +00002330
2331 pj_log_pop_indent();
Benny Prijonoc97608e2007-03-23 16:34:20 +00002332}
2333
2334
Benny Prijono0bc99a92011-03-17 04:34:43 +00002335static pj_status_t audio_channel_update(pjsua_call_media *call_med,
2336 pj_pool_t *tmp_pool,
2337 const pjmedia_sdp_session *local_sdp,
2338 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00002339{
Benny Prijono0bc99a92011-03-17 04:34:43 +00002340 pjsua_call *call = call_med->call;
2341 pjmedia_stream_info the_si, *si = &the_si;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002342 pjmedia_port *media_port;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002343 unsigned strm_idx = call_med->idx;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002344 pj_status_t status;
Benny Prijonob90fd382011-09-18 14:59:56 +00002345
2346 PJ_LOG(4,(THIS_FILE,"Audio channel update.."));
2347 pj_log_push_indent();
Benny Prijono0bc99a92011-03-17 04:34:43 +00002348
2349 status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
2350 local_sdp, remote_sdp, strm_idx);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002351 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002352 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002353
Sauw Minge7dbbc82011-10-24 09:28:13 +00002354 si->rtcp_sdes_bye_disabled = PJ_TRUE;
2355
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002356 /* Check if no media is active */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002357 if (si->dir == PJMEDIA_DIR_NONE) {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002358 /* Call media state */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002359 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002360
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002361 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002362 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijono68f9e4f2008-03-21 08:56:02 +00002363
Benny Prijonoc97608e2007-03-23 16:34:20 +00002364 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002365 pjmedia_transport_info tp_info;
2366
Benny Prijono224b4e22008-06-19 14:10:28 +00002367 /* Start/restart media transport */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002368 status = pjmedia_transport_media_start(call_med->tp,
2369 tmp_pool, local_sdp,
2370 remote_sdp, strm_idx);
Benny Prijonod8179652008-01-23 20:39:07 +00002371 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002372 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002373
Sauw Ming73ecfe82011-09-21 10:20:01 +00002374 set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING);
Benny Prijono224b4e22008-06-19 14:10:28 +00002375
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002376 /* Get remote SRTP usage policy */
2377 pjmedia_transport_info_init(&tp_info);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002378 pjmedia_transport_get_info(call_med->tp, &tp_info);
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002379 if (tp_info.specific_info_cnt > 0) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +00002380 unsigned i;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002381 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
2382 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
2383 {
2384 pjmedia_srtp_info *srtp_info =
2385 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
2386
Benny Prijono0bc99a92011-03-17 04:34:43 +00002387 call_med->rem_srtp_use = srtp_info->peer_use;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002388 break;
2389 }
2390 }
2391 }
2392
Benny Prijonoc97608e2007-03-23 16:34:20 +00002393 /* Override ptime, if this option is specified. */
2394 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00002395 si->param->setting.frm_per_pkt = (pj_uint8_t)
2396 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
2397 if (si->param->setting.frm_per_pkt == 0)
2398 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002399 }
2400
2401 /* Disable VAD, if this option is specified. */
2402 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00002403 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002404 }
2405
2406
2407 /* Optionally, application may modify other stream settings here
2408 * (such as jitter buffer parameters, codec ptime, etc.)
2409 */
Benny Prijono91e567e2007-12-28 08:51:58 +00002410 si->jb_init = pjsua_var.media_cfg.jb_init;
2411 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
2412 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
2413 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002414
Benny Prijono8147f402007-11-21 14:50:07 +00002415 /* Set SSRC */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002416 si->ssrc = call_med->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00002417
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002418 /* Set RTP timestamp & sequence, normally these value are intialized
2419 * automatically when stream session created, but for some cases (e.g:
2420 * call reinvite, call update) timestamp and sequence need to be kept
2421 * contigue.
2422 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002423 si->rtp_ts = call_med->rtp_tx_ts;
2424 si->rtp_seq = call_med->rtp_tx_seq;
2425 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002426
Nanang Izzuddin5e39a2b2010-09-20 06:13:02 +00002427#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
2428 /* Enable/disable stream keep-alive and NAT hole punch. */
2429 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
2430#endif
2431
Benny Prijonoc97608e2007-03-23 16:34:20 +00002432 /* Create session based on session info. */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002433 status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,
2434 call_med->tp, NULL,
2435 &call_med->strm.a.stream);
2436 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002437 goto on_return;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002438 }
2439
2440 /* Start stream */
2441 status = pjmedia_stream_start(call_med->strm.a.stream);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002442 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002443 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002444 }
2445
Sauw Minge7dbbc82011-10-24 09:28:13 +00002446 if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE)
2447 pjmedia_stream_send_rtcp_sdes(call_med->strm.a.stream);
2448
Benny Prijonoc97608e2007-03-23 16:34:20 +00002449 /* If DTMF callback is installed by application, install our
2450 * callback to the session.
2451 */
2452 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002453 pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
2454 &dtmf_callback,
2455 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00002456 }
2457
2458 /* Get the port interface of the first stream in the session.
2459 * We need the port interface to add to the conference bridge.
2460 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002461 pjmedia_stream_get_port(call_med->strm.a.stream, &media_port);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002462
Benny Prijonofc13bf62008-02-20 08:56:15 +00002463 /* Notify application about stream creation.
2464 * Note: application may modify media_port to point to different
2465 * media port
2466 */
2467 if (pjsua_var.ua_cfg.cb.on_stream_created) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002468 pjsua_var.ua_cfg.cb.on_stream_created(call->index,
2469 call_med->strm.a.stream,
2470 strm_idx, &media_port);
Benny Prijonofc13bf62008-02-20 08:56:15 +00002471 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002472
2473 /*
2474 * Add the call to conference bridge.
2475 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002476 {
2477 char tmp[PJSIP_MAX_URL_SIZE];
2478 pj_str_t port_name;
2479
2480 port_name.ptr = tmp;
2481 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
2482 call->inv->dlg->remote.info->uri,
2483 tmp, sizeof(tmp));
2484 if (port_name.slen < 1) {
2485 port_name = pj_str("call");
2486 }
Benny Prijono40d62b62009-08-12 17:53:47 +00002487 status = pjmedia_conf_add_port( pjsua_var.mconf,
2488 call->inv->pool_prov,
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002489 media_port,
2490 &port_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +00002491 (unsigned*)
2492 &call_med->strm.a.conf_slot);
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002493 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002494 goto on_return;
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002495 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002496 }
2497
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002498 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002499 call_med->dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002500
2501 /* Call media state */
2502 if (call->local_hold)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002503 call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
2504 else if (call_med->dir == PJMEDIA_DIR_DECODING)
2505 call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002506 else
Benny Prijono0bc99a92011-03-17 04:34:43 +00002507 call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002508 }
2509
2510 /* Print info. */
2511 {
2512 char info[80];
2513 int info_len = 0;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002514 int len;
2515 const char *dir;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002516
Benny Prijono0bc99a92011-03-17 04:34:43 +00002517 switch (si->dir) {
2518 case PJMEDIA_DIR_NONE:
2519 dir = "inactive";
2520 break;
2521 case PJMEDIA_DIR_ENCODING:
2522 dir = "sendonly";
2523 break;
2524 case PJMEDIA_DIR_DECODING:
2525 dir = "recvonly";
2526 break;
2527 case PJMEDIA_DIR_ENCODING_DECODING:
2528 dir = "sendrecv";
2529 break;
2530 default:
2531 dir = "unknown";
2532 break;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002533 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00002534 len = pj_ansi_sprintf( info+info_len,
2535 ", stream #%d: %.*s (%s)", strm_idx,
2536 (int)si->fmt.encoding_name.slen,
2537 si->fmt.encoding_name.ptr,
2538 dir);
2539 if (len > 0)
2540 info_len += len;
Benny Prijonob90fd382011-09-18 14:59:56 +00002541 PJ_LOG(4,(THIS_FILE,"Audio updated%s", info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00002542 }
2543
Benny Prijonob90fd382011-09-18 14:59:56 +00002544on_return:
2545 pj_log_pop_indent();
2546 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002547}
2548
Benny Prijono0bc99a92011-03-17 04:34:43 +00002549pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
2550 const pjmedia_sdp_session *local_sdp,
2551 const pjmedia_sdp_session *remote_sdp)
2552{
2553 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002554 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002555 pj_pool_t *tmp_pool = call->inv->pool_prov;
2556 unsigned mi;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002557 pj_bool_t got_media = PJ_FALSE;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002558 pj_status_t status = PJ_SUCCESS;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002559
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002560 const pj_str_t STR_AUDIO = { "audio", 5 };
2561 const pj_str_t STR_VIDEO = { "video", 5 };
2562 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
2563 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002564 unsigned mtotaudcnt = PJ_ARRAY_SIZE(maudidx);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002565 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
2566 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002567 unsigned mtotvidcnt = PJ_ARRAY_SIZE(mvididx);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002568 pj_bool_t need_renego_sdp = PJ_FALSE;
2569
Benny Prijono0bc99a92011-03-17 04:34:43 +00002570 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
2571 return PJ_EBUSY;
2572
Benny Prijonob90fd382011-09-18 14:59:56 +00002573 PJ_LOG(4,(THIS_FILE, "Call %d: updating media..", call_id));
2574 pj_log_push_indent();
2575
Benny Prijono0bc99a92011-03-17 04:34:43 +00002576 /* Destroy existing media session, if any. */
2577 stop_media_session(call->index);
2578
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002579 /* Call media count must be at least equal to SDP media. Note that
2580 * it may not be equal when remote removed any SDP media line.
2581 */
2582 pj_assert(call->med_cnt >= local_sdp->media_count);
2583
Benny Prijono0bc99a92011-03-17 04:34:43 +00002584 /* Reset audio_idx first */
2585 call->audio_idx = -1;
2586
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002587 /* Sort audio/video based on "quality" */
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002588 sort_media(local_sdp, &STR_AUDIO, acc->cfg.use_srtp,
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002589 maudidx, &maudcnt, &mtotaudcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002590#if PJMEDIA_HAS_VIDEO
2591 sort_media(local_sdp, &STR_VIDEO, acc->cfg.use_srtp,
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002592 mvididx, &mvidcnt, &mtotvidcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002593#else
2594 PJ_UNUSED_ARG(STR_VIDEO);
Nanang Izzuddin3d7385c2011-12-01 10:02:54 +00002595 mvidcnt = mtotvidcnt = 0;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002596#endif
Nanang Izzuddindebd48a2011-12-01 09:06:14 +00002597
2598 /* Applying media count limitation. Note that in generating SDP answer,
2599 * no media count limitation applied, as we didn't know yet which media
2600 * would pass the SDP negotiation.
2601 */
Benny Prijonoae86e6a2011-12-27 12:47:52 +00002602 if (maudcnt > call->opt.aud_cnt || mvidcnt > call->opt.vid_cnt)
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002603 {
2604 pjmedia_sdp_session *local_sdp2;
2605
Benny Prijonoae86e6a2011-12-27 12:47:52 +00002606 maudcnt = PJ_MIN(maudcnt, call->opt.aud_cnt);
2607 mvidcnt = PJ_MIN(mvidcnt, call->opt.vid_cnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002608 local_sdp2 = pjmedia_sdp_session_clone(tmp_pool, local_sdp);
2609
2610 for (mi=0; mi < local_sdp2->media_count; ++mi) {
2611 pjmedia_sdp_media *m = local_sdp2->media[mi];
2612
2613 if (m->desc.port == 0 ||
2614 pj_memchr(maudidx, mi, maudcnt*sizeof(maudidx[0])) ||
2615 pj_memchr(mvididx, mi, mvidcnt*sizeof(mvididx[0])))
2616 {
2617 continue;
2618 }
2619
2620 /* Deactivate this media */
2621 pjmedia_sdp_media_deactivate(tmp_pool, m);
2622 }
2623
2624 local_sdp = local_sdp2;
2625 need_renego_sdp = PJ_TRUE;
2626 }
2627
Benny Prijono0bc99a92011-03-17 04:34:43 +00002628 /* Process each media stream */
2629 for (mi=0; mi < call->med_cnt; ++mi) {
2630 pjsua_call_media *call_med = &call->media[mi];
2631
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002632 if (mi >= local_sdp->media_count ||
2633 mi >= remote_sdp->media_count)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002634 {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002635 /* This may happen when remote removed any SDP media lines in
2636 * its re-offer.
2637 */
2638 continue;
2639#if 0
Benny Prijono0bc99a92011-03-17 04:34:43 +00002640 /* Something is wrong */
2641 PJ_LOG(1,(THIS_FILE, "Error updating media for call %d: "
2642 "invalid media index %d in SDP", call_id, mi));
Benny Prijonob90fd382011-09-18 14:59:56 +00002643 status = PJMEDIA_SDP_EINSDP;
2644 goto on_error;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002645#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002646 }
2647
2648 switch (call_med->type) {
2649 case PJMEDIA_TYPE_AUDIO:
2650 status = audio_channel_update(call_med, tmp_pool,
2651 local_sdp, remote_sdp);
2652 if (call->audio_idx==-1 && status==PJ_SUCCESS &&
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002653 call_med->strm.a.stream)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002654 {
2655 call->audio_idx = mi;
2656 }
2657 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00002658#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002659 case PJMEDIA_TYPE_VIDEO:
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002660 status = video_channel_update(call_med, tmp_pool,
2661 local_sdp, remote_sdp);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002662 break;
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002663#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002664 default:
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002665 status = PJMEDIA_EINVALIMEDIATYPE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002666 break;
2667 }
2668
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002669 /* Close the transport of deactivated media, need this here as media
2670 * can be deactivated by the SDP negotiation and the max media count
2671 * (account) setting.
2672 */
2673 if (local_sdp->media[mi]->desc.port==0 && call_med->tp) {
2674 pjmedia_transport_close(call_med->tp);
2675 call_med->tp = call_med->tp_orig = NULL;
Sauw Ming73ecfe82011-09-21 10:20:01 +00002676 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002677 }
2678
Benny Prijono0bc99a92011-03-17 04:34:43 +00002679 if (status != PJ_SUCCESS) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002680 PJ_PERROR(1,(THIS_FILE, status, "Error updating media call%02d:%d",
Benny Prijono0bc99a92011-03-17 04:34:43 +00002681 call_id, mi));
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002682 } else {
2683 got_media = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002684 }
2685 }
2686
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002687 /* Perform SDP re-negotiation if needed. */
2688 if (got_media && need_renego_sdp) {
2689 pjmedia_sdp_neg *neg = call->inv->neg;
2690
2691 /* This should only happen when we are the answerer. */
2692 PJ_ASSERT_RETURN(neg && !pjmedia_sdp_neg_was_answer_remote(neg),
2693 PJMEDIA_SDPNEG_EINSTATE);
2694
2695 status = pjmedia_sdp_neg_set_remote_offer(tmp_pool, neg, remote_sdp);
2696 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002697 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002698
2699 status = pjmedia_sdp_neg_set_local_answer(tmp_pool, neg, local_sdp);
2700 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002701 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002702
2703 status = pjmedia_sdp_neg_negotiate(tmp_pool, neg, 0);
2704 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002705 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002706 }
2707
Benny Prijonob90fd382011-09-18 14:59:56 +00002708 pj_log_pop_indent();
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002709 return (got_media? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA);
Benny Prijonob90fd382011-09-18 14:59:56 +00002710
2711on_error:
2712 pj_log_pop_indent();
2713 return status;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002714}
2715
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002716/*
2717 * Get maxinum number of conference ports.
2718 */
2719PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
2720{
2721 return pjsua_var.media_cfg.max_media_ports;
2722}
2723
2724
2725/*
2726 * Get current number of active ports in the bridge.
2727 */
2728PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
2729{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002730 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002731 unsigned count = PJ_ARRAY_SIZE(ports);
2732 pj_status_t status;
2733
2734 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
2735 if (status != PJ_SUCCESS)
2736 count = 0;
2737
2738 return count;
2739}
2740
2741
2742/*
2743 * Enumerate all conference ports.
2744 */
2745PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
2746 unsigned *count)
2747{
2748 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
2749}
2750
2751
2752/*
2753 * Get information about the specified conference port
2754 */
2755PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
2756 pjsua_conf_port_info *info)
2757{
2758 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00002759 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002760 pj_status_t status;
2761
2762 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
2763 if (status != PJ_SUCCESS)
2764 return status;
2765
Benny Prijonoac623b32006-07-03 15:19:31 +00002766 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002767 info->slot_id = id;
2768 info->name = cinfo.name;
2769 info->clock_rate = cinfo.clock_rate;
2770 info->channel_count = cinfo.channel_count;
2771 info->samples_per_frame = cinfo.samples_per_frame;
2772 info->bits_per_sample = cinfo.bits_per_sample;
2773
2774 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00002775 info->listener_cnt = cinfo.listener_cnt;
2776 for (i=0; i<cinfo.listener_cnt; ++i) {
2777 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002778 }
2779
2780 return PJ_SUCCESS;
2781}
2782
2783
2784/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00002785 * Add arbitrary media port to PJSUA's conference bridge.
2786 */
2787PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
2788 pjmedia_port *port,
2789 pjsua_conf_port_id *p_id)
2790{
2791 pj_status_t status;
2792
2793 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
2794 port, NULL, (unsigned*)p_id);
2795 if (status != PJ_SUCCESS) {
2796 if (p_id)
2797 *p_id = PJSUA_INVALID_ID;
2798 }
2799
2800 return status;
2801}
2802
2803
2804/*
2805 * Remove arbitrary slot from the conference bridge.
2806 */
2807PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
2808{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002809 pj_status_t status;
2810
2811 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
2812 check_snd_dev_idle();
2813
2814 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00002815}
2816
2817
2818/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002819 * Establish unidirectional media flow from souce to sink.
2820 */
2821PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
2822 pjsua_conf_port_id sink)
2823{
Benny Prijonob90fd382011-09-18 14:59:56 +00002824 pj_status_t status = PJ_SUCCESS;
2825
2826 PJ_LOG(4,(THIS_FILE, "%s connect: %d --> %d",
2827 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
2828 source, sink));
2829 pj_log_push_indent();
2830
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002831 /* If sound device idle timer is active, cancel it first. */
Benny Prijono0f711b42009-05-06 19:08:43 +00002832 PJSUA_LOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002833 if (pjsua_var.snd_idle_timer.id) {
2834 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
2835 pjsua_var.snd_idle_timer.id = PJ_FALSE;
2836 }
Benny Prijono0f711b42009-05-06 19:08:43 +00002837 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002838
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002839
Benny Prijonof798e502009-03-09 13:08:16 +00002840 /* For audio switchboard (i.e. APS-Direct):
2841 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002842 * (format, clock rate, channel count) must match to peer's.
2843 * Note that sound device can be reopened only if it doesn't have
2844 * any connection.
2845 */
Benny Prijonof798e502009-03-09 13:08:16 +00002846 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002847 pjmedia_conf_port_info port0_info;
2848 pjmedia_conf_port_info peer_info;
2849 unsigned peer_id;
2850 pj_bool_t need_reopen = PJ_FALSE;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002851
2852 peer_id = (source!=0)? source : sink;
2853 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
2854 &peer_info);
2855 pj_assert(status == PJ_SUCCESS);
2856
2857 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
2858 pj_assert(status == PJ_SUCCESS);
2859
2860 /* Check if sound device is instantiated. */
2861 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2862 !pjsua_var.no_snd);
2863
2864 /* Check if sound device need to reopen because it needs to modify
2865 * settings to match its peer. Sound device must be idle in this case
2866 * though.
2867 */
2868 if (!need_reopen &&
2869 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
2870 {
2871 need_reopen = (peer_info.format.id != port0_info.format.id ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002872 peer_info.format.det.aud.avg_bps !=
2873 port0_info.format.det.aud.avg_bps ||
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002874 peer_info.clock_rate != port0_info.clock_rate ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002875 peer_info.channel_count!=port0_info.channel_count);
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002876 }
2877
2878 if (need_reopen) {
Benny Prijonod65f78c2009-06-03 18:59:37 +00002879 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
Sauw Ming98766c72011-03-11 06:57:24 +00002880 pjmedia_snd_port_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002881
Benny Prijonod65f78c2009-06-03 18:59:37 +00002882 /* Create parameter based on peer info */
Sauw Ming98766c72011-03-11 06:57:24 +00002883 status = create_aud_param(&param.base, pjsua_var.cap_dev,
Benny Prijonod65f78c2009-06-03 18:59:37 +00002884 pjsua_var.play_dev,
2885 peer_info.clock_rate,
2886 peer_info.channel_count,
2887 peer_info.samples_per_frame,
2888 peer_info.bits_per_sample);
2889 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002890 pjsua_perror(THIS_FILE, "Error opening sound device",
2891 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002892 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002893 }
Benny Prijonof798e502009-03-09 13:08:16 +00002894
Benny Prijonod65f78c2009-06-03 18:59:37 +00002895 /* And peer format */
2896 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
Sauw Ming98766c72011-03-11 06:57:24 +00002897 param.base.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
2898 param.base.ext_fmt = peer_info.format;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002899 }
Benny Prijono10454dc2009-02-21 14:21:59 +00002900
Sauw Ming98766c72011-03-11 06:57:24 +00002901 param.options = 0;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002902 status = open_snd_dev(&param);
2903 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002904 pjsua_perror(THIS_FILE, "Error opening sound device",
2905 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002906 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002907 }
2908 } else {
2909 /* Null-audio */
Benny Prijonoc45d9512010-12-10 11:04:30 +00002910 status = pjsua_set_snd_dev(pjsua_var.cap_dev,
2911 pjsua_var.play_dev);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002912 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002913 pjsua_perror(THIS_FILE, "Error opening sound device",
2914 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002915 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002916 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002917 }
Benny Prijono2d647722011-07-13 03:05:22 +00002918 } else if (pjsua_var.no_snd) {
2919 if (!pjsua_var.snd_is_on) {
2920 pjsua_var.snd_is_on = PJ_TRUE;
2921 /* Notify app */
2922 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2923 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2924 }
2925 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002926 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002927
Benny Prijonof798e502009-03-09 13:08:16 +00002928 } else {
2929 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002930
Benny Prijonof798e502009-03-09 13:08:16 +00002931 /* Create sound port if none is instantiated */
2932 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2933 !pjsua_var.no_snd)
2934 {
2935 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002936
Benny Prijonof798e502009-03-09 13:08:16 +00002937 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
2938 if (status != PJ_SUCCESS) {
2939 pjsua_perror(THIS_FILE, "Error opening sound device", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002940 goto on_return;
Benny Prijonof798e502009-03-09 13:08:16 +00002941 }
Benny Prijono2d647722011-07-13 03:05:22 +00002942 } else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) {
2943 pjsua_var.snd_is_on = PJ_TRUE;
2944 /* Notify app */
2945 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2946 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2947 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002948 }
Benny Prijonof798e502009-03-09 13:08:16 +00002949 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002950
Benny Prijonob90fd382011-09-18 14:59:56 +00002951 status = pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
2952
2953on_return:
2954 pj_log_pop_indent();
2955 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002956}
2957
2958
2959/*
2960 * Disconnect media flow from the source to destination port.
2961 */
2962PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
2963 pjsua_conf_port_id sink)
2964{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002965 pj_status_t status;
2966
Benny Prijonob90fd382011-09-18 14:59:56 +00002967 PJ_LOG(4,(THIS_FILE, "%s disconnect: %d -x- %d",
2968 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
2969 source, sink));
2970 pj_log_push_indent();
2971
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002972 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002973 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002974
Benny Prijonob90fd382011-09-18 14:59:56 +00002975 pj_log_pop_indent();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002976 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002977}
2978
2979
Benny Prijono6dd967c2006-12-26 02:27:14 +00002980/*
2981 * Adjust the signal level to be transmitted from the bridge to the
2982 * specified port by making it louder or quieter.
2983 */
2984PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
2985 float level)
2986{
2987 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
2988 (int)((level-1) * 128));
2989}
2990
2991/*
2992 * Adjust the signal level to be received from the specified port (to
2993 * the bridge) by making it louder or quieter.
2994 */
2995PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
2996 float level)
2997{
2998 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
2999 (int)((level-1) * 128));
3000}
3001
3002
3003/*
3004 * Get last signal level transmitted to or received from the specified port.
3005 */
3006PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
3007 unsigned *tx_level,
3008 unsigned *rx_level)
3009{
3010 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
3011 tx_level, rx_level);
3012}
3013
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003014/*****************************************************************************
3015 * File player.
3016 */
3017
Benny Prijonod5696da2007-07-17 16:25:45 +00003018static char* get_basename(const char *path, unsigned len)
3019{
3020 char *p = ((char*)path) + len;
3021
3022 if (len==0)
3023 return p;
3024
Benny Prijono1f61a8f2007-08-16 10:11:44 +00003025 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00003026
3027 return (p==path) ? p : p+1;
3028}
3029
3030
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003031/*
3032 * Create a file player, and automatically connect this player to
3033 * the conference bridge.
3034 */
3035PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
3036 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003037 pjsua_player_id *p_id)
3038{
3039 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003040 char path[PJ_MAXPATH];
Benny Prijonob90fd382011-09-18 14:59:56 +00003041 pj_pool_t *pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003042 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00003043 pj_status_t status = PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003044
3045 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
3046 return PJ_ETOOMANY;
3047
Benny Prijonob90fd382011-09-18 14:59:56 +00003048 PJ_LOG(4,(THIS_FILE, "Creating file player: %.*s..",
3049 (int)filename->slen, filename->ptr));
3050 pj_log_push_indent();
3051
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003052 PJSUA_LOCK();
3053
3054 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
3055 if (pjsua_var.player[file_id].port == NULL)
3056 break;
3057 }
3058
3059 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
3060 /* This is unexpected */
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003061 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00003062 status = PJ_EBUG;
3063 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003064 }
3065
3066 pj_memcpy(path, filename->ptr, filename->slen);
3067 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00003068
3069 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
3070 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003071 status = PJ_ENOMEM;
3072 goto on_error;
Benny Prijonod5696da2007-07-17 16:25:45 +00003073 }
3074
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00003075 status = pjmedia_wav_player_port_create(
3076 pool, path,
3077 pjsua_var.mconf_cfg.samples_per_frame *
Benny Prijonoc45d9512010-12-10 11:04:30 +00003078 1000 / pjsua_var.media_cfg.channel_count /
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00003079 pjsua_var.media_cfg.clock_rate,
3080 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003081 if (status != PJ_SUCCESS) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003082 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003083 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003084 }
3085
Benny Prijono5297af92008-03-18 13:40:40 +00003086 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003087 port, filename, &slot);
3088 if (status != PJ_SUCCESS) {
3089 pjmedia_port_destroy(port);
Benny Prijono32e4f492007-01-24 00:44:26 +00003090 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
3091 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003092 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003093 }
3094
Benny Prijonoa66c3312007-01-21 23:12:40 +00003095 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00003096 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003097 pjsua_var.player[file_id].port = port;
3098 pjsua_var.player[file_id].slot = slot;
3099
3100 if (p_id) *p_id = file_id;
3101
3102 ++pjsua_var.player_cnt;
3103
3104 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003105
3106 PJ_LOG(4,(THIS_FILE, "Player created, id=%d, slot=%d", file_id, slot));
3107
3108 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003109 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003110
3111on_error:
3112 PJSUA_UNLOCK();
3113 if (pool) pj_pool_release(pool);
3114 pj_log_pop_indent();
3115 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003116}
3117
3118
3119/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00003120 * Create a file playlist media port, and automatically add the port
3121 * to the conference bridge.
3122 */
3123PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
3124 unsigned file_count,
3125 const pj_str_t *label,
3126 unsigned options,
3127 pjsua_player_id *p_id)
3128{
3129 unsigned slot, file_id, ptime;
Benny Prijonob90fd382011-09-18 14:59:56 +00003130 pj_pool_t *pool = NULL;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003131 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00003132 pj_status_t status = PJ_SUCCESS;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003133
3134 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
3135 return PJ_ETOOMANY;
3136
Benny Prijonob90fd382011-09-18 14:59:56 +00003137 PJ_LOG(4,(THIS_FILE, "Creating playlist with %d file(s)..", file_count));
3138 pj_log_push_indent();
3139
Benny Prijonoa66c3312007-01-21 23:12:40 +00003140 PJSUA_LOCK();
3141
3142 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
3143 if (pjsua_var.player[file_id].port == NULL)
3144 break;
3145 }
3146
3147 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
3148 /* This is unexpected */
Benny Prijonoa66c3312007-01-21 23:12:40 +00003149 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00003150 status = PJ_EBUG;
3151 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003152 }
3153
3154
3155 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
3156 pjsua_var.media_cfg.clock_rate;
3157
Benny Prijonod5696da2007-07-17 16:25:45 +00003158 pool = pjsua_pool_create("playlist", 1000, 1000);
3159 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003160 status = PJ_ENOMEM;
3161 goto on_error;
Benny Prijonod5696da2007-07-17 16:25:45 +00003162 }
3163
3164 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00003165 file_names, file_count,
3166 ptime, options, 0, &port);
3167 if (status != PJ_SUCCESS) {
Benny Prijonoa66c3312007-01-21 23:12:40 +00003168 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003169 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003170 }
3171
Benny Prijonod5696da2007-07-17 16:25:45 +00003172 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00003173 port, &port->info.name, &slot);
3174 if (status != PJ_SUCCESS) {
3175 pjmedia_port_destroy(port);
Benny Prijonoa66c3312007-01-21 23:12:40 +00003176 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003177 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003178 }
3179
3180 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00003181 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003182 pjsua_var.player[file_id].port = port;
3183 pjsua_var.player[file_id].slot = slot;
3184
3185 if (p_id) *p_id = file_id;
3186
3187 ++pjsua_var.player_cnt;
3188
3189 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003190
3191 PJ_LOG(4,(THIS_FILE, "Playlist created, id=%d, slot=%d", file_id, slot));
3192
3193 pj_log_pop_indent();
3194
Benny Prijonoa66c3312007-01-21 23:12:40 +00003195 return PJ_SUCCESS;
3196
Benny Prijonob90fd382011-09-18 14:59:56 +00003197on_error:
3198 PJSUA_UNLOCK();
3199 if (pool) pj_pool_release(pool);
3200 pj_log_pop_indent();
3201
3202 return status;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003203}
3204
3205
3206/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003207 * Get conference port ID associated with player.
3208 */
3209PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
3210{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003211 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003212 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
3213
3214 return pjsua_var.player[id].slot;
3215}
3216
Benny Prijono469b1522006-12-26 03:05:17 +00003217/*
3218 * Get the media port for the player.
3219 */
Benny Prijonobe41d862008-01-18 13:24:28 +00003220PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00003221 pjmedia_port **p_port)
3222{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003223 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00003224 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
3225 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
3226
3227 *p_port = pjsua_var.player[id].port;
3228
3229 return PJ_SUCCESS;
3230}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003231
3232/*
3233 * Set playback position.
3234 */
3235PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
3236 pj_uint32_t samples)
3237{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003238 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003239 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00003240 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003241
3242 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
3243}
3244
3245
3246/*
3247 * Close the file, remove the player from the bridge, and free
3248 * resources associated with the file player.
3249 */
3250PJ_DEF(pj_status_t) pjsua_player_destroy(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
Benny Prijonob90fd382011-09-18 14:59:56 +00003255 PJ_LOG(4,(THIS_FILE, "Destroying player %d..", id));
3256 pj_log_push_indent();
3257
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003258 PJSUA_LOCK();
3259
3260 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00003261 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003262 pjmedia_port_destroy(pjsua_var.player[id].port);
3263 pjsua_var.player[id].port = NULL;
3264 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00003265 pj_pool_release(pjsua_var.player[id].pool);
3266 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003267 pjsua_var.player_cnt--;
3268 }
3269
3270 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003271 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003272
3273 return PJ_SUCCESS;
3274}
3275
3276
3277/*****************************************************************************
3278 * File recorder.
3279 */
3280
3281/*
3282 * Create a file recorder, and automatically connect this recorder to
3283 * the conference bridge.
3284 */
3285PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00003286 unsigned enc_type,
3287 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003288 pj_ssize_t max_size,
3289 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003290 pjsua_recorder_id *p_id)
3291{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003292 enum Format
3293 {
3294 FMT_UNKNOWN,
3295 FMT_WAV,
3296 FMT_MP3,
3297 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003298 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003299 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003300 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00003301 int file_format;
Benny Prijonob90fd382011-09-18 14:59:56 +00003302 pj_pool_t *pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003303 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00003304 pj_status_t status = PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003305
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003306 /* Filename must present */
3307 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
3308
Benny Prijono00cae612006-07-31 15:19:36 +00003309 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003310 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00003311
Benny Prijono8f310522006-10-20 11:08:49 +00003312 /* Don't support encoding type at present */
3313 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00003314
Benny Prijonob90fd382011-09-18 14:59:56 +00003315 PJ_LOG(4,(THIS_FILE, "Creating recorder %.*s..",
3316 (int)filename->slen, filename->ptr));
3317 pj_log_push_indent();
3318
3319 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) {
3320 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003321 return PJ_ETOOMANY;
Benny Prijonob90fd382011-09-18 14:59:56 +00003322 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003323
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003324 /* Determine the file format */
3325 ext.ptr = filename->ptr + filename->slen - 4;
3326 ext.slen = 4;
3327
3328 if (pj_stricmp2(&ext, ".wav") == 0)
3329 file_format = FMT_WAV;
3330 else if (pj_stricmp2(&ext, ".mp3") == 0)
3331 file_format = FMT_MP3;
3332 else {
3333 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
3334 "determine file format for %.*s",
3335 (int)filename->slen, filename->ptr));
Benny Prijonob90fd382011-09-18 14:59:56 +00003336 pj_log_pop_indent();
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003337 return PJ_ENOTSUP;
3338 }
3339
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003340 PJSUA_LOCK();
3341
3342 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
3343 if (pjsua_var.recorder[file_id].port == NULL)
3344 break;
3345 }
3346
3347 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
3348 /* This is unexpected */
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003349 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00003350 status = PJ_EBUG;
3351 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003352 }
3353
3354 pj_memcpy(path, filename->ptr, filename->slen);
3355 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003356
Benny Prijonod5696da2007-07-17 16:25:45 +00003357 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
3358 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003359 status = PJ_ENOMEM;
3360 goto on_return;
Benny Prijonod5696da2007-07-17 16:25:45 +00003361 }
3362
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003363 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00003364 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003365 pjsua_var.media_cfg.clock_rate,
3366 pjsua_var.mconf_cfg.channel_count,
3367 pjsua_var.mconf_cfg.samples_per_frame,
3368 pjsua_var.mconf_cfg.bits_per_sample,
3369 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003370 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00003371 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003372 port = NULL;
3373 status = PJ_ENOTSUP;
3374 }
3375
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003376 if (status != PJ_SUCCESS) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003377 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003378 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003379 }
3380
Benny Prijonod5696da2007-07-17 16:25:45 +00003381 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003382 port, filename, &slot);
3383 if (status != PJ_SUCCESS) {
3384 pjmedia_port_destroy(port);
Benny Prijonob90fd382011-09-18 14:59:56 +00003385 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003386 }
3387
3388 pjsua_var.recorder[file_id].port = port;
3389 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00003390 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003391
3392 if (p_id) *p_id = file_id;
3393
3394 ++pjsua_var.rec_cnt;
3395
3396 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003397
3398 PJ_LOG(4,(THIS_FILE, "Recorder created, id=%d, slot=%d", file_id, slot));
3399
3400 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003401 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003402
3403on_return:
3404 PJSUA_UNLOCK();
3405 if (pool) pj_pool_release(pool);
3406 pj_log_pop_indent();
3407 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003408}
3409
3410
3411/*
3412 * Get conference port associated with recorder.
3413 */
3414PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
3415{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003416 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3417 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003418 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3419
3420 return pjsua_var.recorder[id].slot;
3421}
3422
Benny Prijono469b1522006-12-26 03:05:17 +00003423/*
3424 * Get the media port for the recorder.
3425 */
3426PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
3427 pjmedia_port **p_port)
3428{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003429 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3430 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00003431 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3432 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
3433
3434 *p_port = pjsua_var.recorder[id].port;
3435 return PJ_SUCCESS;
3436}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003437
3438/*
3439 * Destroy recorder (this will complete recording).
3440 */
3441PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
3442{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003443 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3444 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003445 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3446
Benny Prijonob90fd382011-09-18 14:59:56 +00003447 PJ_LOG(4,(THIS_FILE, "Destroying recorder %d..", id));
3448 pj_log_push_indent();
3449
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003450 PJSUA_LOCK();
3451
3452 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00003453 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003454 pjmedia_port_destroy(pjsua_var.recorder[id].port);
3455 pjsua_var.recorder[id].port = NULL;
3456 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00003457 pj_pool_release(pjsua_var.recorder[id].pool);
3458 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003459 pjsua_var.rec_cnt--;
3460 }
3461
3462 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003463 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003464
3465 return PJ_SUCCESS;
3466}
3467
3468
3469/*****************************************************************************
3470 * Sound devices.
3471 */
3472
3473/*
3474 * Enum sound devices.
3475 */
Benny Prijonof798e502009-03-09 13:08:16 +00003476
3477PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003478 unsigned *count)
3479{
3480 unsigned i, dev_count;
3481
Benny Prijono10454dc2009-02-21 14:21:59 +00003482 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003483
3484 if (dev_count > *count) dev_count = *count;
3485
3486 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00003487 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003488
Benny Prijono10454dc2009-02-21 14:21:59 +00003489 status = pjmedia_aud_dev_get_info(i, &info[i]);
3490 if (status != PJ_SUCCESS)
3491 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003492 }
3493
3494 *count = dev_count;
3495
3496 return PJ_SUCCESS;
3497}
Benny Prijonof798e502009-03-09 13:08:16 +00003498
3499
Benny Prijono10454dc2009-02-21 14:21:59 +00003500PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
3501 unsigned *count)
3502{
3503 unsigned i, dev_count;
3504
3505 dev_count = pjmedia_aud_dev_count();
3506
3507 if (dev_count > *count) dev_count = *count;
3508 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
3509
3510 for (i=0; i<dev_count; ++i) {
3511 pjmedia_aud_dev_info ai;
3512 pj_status_t status;
3513
3514 status = pjmedia_aud_dev_get_info(i, &ai);
3515 if (status != PJ_SUCCESS)
3516 return status;
3517
3518 strncpy(info[i].name, ai.name, sizeof(info[i].name));
3519 info[i].name[sizeof(info[i].name)-1] = '\0';
3520 info[i].input_count = ai.input_count;
3521 info[i].output_count = ai.output_count;
3522 info[i].default_samples_per_sec = ai.default_samples_per_sec;
3523 }
3524
3525 *count = dev_count;
3526
3527 return PJ_SUCCESS;
3528}
Benny Prijono10454dc2009-02-21 14:21:59 +00003529
Benny Prijonof798e502009-03-09 13:08:16 +00003530/* Create audio device parameter to open the device */
3531static pj_status_t create_aud_param(pjmedia_aud_param *param,
3532 pjmedia_aud_dev_index capture_dev,
3533 pjmedia_aud_dev_index playback_dev,
3534 unsigned clock_rate,
3535 unsigned channel_count,
3536 unsigned samples_per_frame,
3537 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003538{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003539 pj_status_t status;
3540
Benny Prijono96e74f32009-02-22 12:00:12 +00003541 /* Normalize device ID with new convention about default device ID */
3542 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
3543 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
3544
Benny Prijono10454dc2009-02-21 14:21:59 +00003545 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00003546 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00003547 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00003548 pjsua_perror(THIS_FILE, "Error retrieving default audio "
3549 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00003550 return status;
3551 }
Benny Prijonof798e502009-03-09 13:08:16 +00003552 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
3553 param->rec_id = capture_dev;
3554 param->play_id = playback_dev;
3555 param->clock_rate = clock_rate;
3556 param->channel_count = channel_count;
3557 param->samples_per_frame = samples_per_frame;
3558 param->bits_per_sample = bits_per_sample;
3559
3560 /* Update the setting with user preference */
3561#define update_param(cap, field) \
3562 if (pjsua_var.aud_param.flags & cap) { \
3563 param->flags |= cap; \
3564 param->field = pjsua_var.aud_param.field; \
3565 }
3566 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3567 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3568 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3569 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3570#undef update_param
3571
Benny Prijono10454dc2009-02-21 14:21:59 +00003572 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00003573 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
3574 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
3575 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
3576 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
3577
Benny Prijono10454dc2009-02-21 14:21:59 +00003578 /* EC settings */
3579 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00003580 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
3581 param->ec_enabled = PJ_TRUE;
3582 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00003583 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00003584 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00003585 }
3586
Benny Prijonof798e502009-03-09 13:08:16 +00003587 return PJ_SUCCESS;
3588}
Benny Prijono26056d82006-10-11 16:03:41 +00003589
Benny Prijonof798e502009-03-09 13:08:16 +00003590/* Internal: the first time the audio device is opened (during app
3591 * startup), retrieve the audio settings such as volume level
3592 * so that aud_get_settings() will work.
3593 */
3594static pj_status_t update_initial_aud_param()
3595{
3596 pjmedia_aud_stream *strm;
3597 pjmedia_aud_param param;
3598 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00003599
Benny Prijonof798e502009-03-09 13:08:16 +00003600 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00003601
Benny Prijonof798e502009-03-09 13:08:16 +00003602 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00003603
Benny Prijonof798e502009-03-09 13:08:16 +00003604 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003605 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00003606 pjsua_perror(THIS_FILE, "Error audio stream "
3607 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003608 return status;
3609 }
3610
Benny Prijonof798e502009-03-09 13:08:16 +00003611#define update_saved_param(cap, field) \
3612 if (param.flags & cap) { \
3613 pjsua_var.aud_param.flags |= cap; \
3614 pjsua_var.aud_param.field = param.field; \
3615 }
3616
3617 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3618 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3619 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3620 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3621#undef update_saved_param
3622
3623 return PJ_SUCCESS;
3624}
3625
3626/* Get format name */
3627static const char *get_fmt_name(pj_uint32_t id)
3628{
3629 static char name[8];
3630
3631 if (id == PJMEDIA_FORMAT_L16)
3632 return "PCM";
3633 pj_memcpy(name, &id, 4);
3634 name[4] = '\0';
3635 return name;
3636}
3637
3638/* Open sound device with the setting. */
Sauw Ming98766c72011-03-11 06:57:24 +00003639static pj_status_t open_snd_dev(pjmedia_snd_port_param *param)
Benny Prijonof798e502009-03-09 13:08:16 +00003640{
3641 pjmedia_port *conf_port;
3642 pj_status_t status;
3643
3644 PJ_ASSERT_RETURN(param, PJ_EINVAL);
3645
3646 /* Check if NULL sound device is used */
Sauw Ming98766c72011-03-11 06:57:24 +00003647 if (NULL_SND_DEV_ID==param->base.rec_id ||
3648 NULL_SND_DEV_ID==param->base.play_id)
3649 {
Benny Prijonof798e502009-03-09 13:08:16 +00003650 return pjsua_set_null_snd_dev();
3651 }
3652
3653 /* Close existing sound port */
3654 close_snd_dev();
3655
Benny Prijono2d647722011-07-13 03:05:22 +00003656 /* Notify app */
3657 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3658 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
3659 }
3660
Benny Prijonof798e502009-03-09 13:08:16 +00003661 /* Create memory pool for sound device. */
3662 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3663 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3664
3665
3666 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
Sauw Ming98766c72011-03-11 06:57:24 +00003667 get_fmt_name(param->base.ext_fmt.id),
3668 param->base.clock_rate, param->base.channel_count,
3669 param->base.samples_per_frame / param->base.channel_count *
3670 1000 / param->base.clock_rate));
Benny Prijonob90fd382011-09-18 14:59:56 +00003671 pj_log_push_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003672
3673 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
Sauw Ming98766c72011-03-11 06:57:24 +00003674 param, &pjsua_var.snd_port);
Benny Prijonof798e502009-03-09 13:08:16 +00003675 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00003676 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003677
3678 /* Get the port0 of the conference bridge. */
3679 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3680 pj_assert(conf_port != NULL);
3681
3682 /* For conference bridge, resample if necessary if the bridge's
3683 * clock rate is different than the sound device's clock rate.
3684 */
3685 if (!pjsua_var.is_mswitch &&
Sauw Ming98766c72011-03-11 06:57:24 +00003686 param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM &&
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003687 PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate)
Benny Prijonof798e502009-03-09 13:08:16 +00003688 {
3689 pjmedia_port *resample_port;
3690 unsigned resample_opt = 0;
3691
3692 if (pjsua_var.media_cfg.quality >= 3 &&
3693 pjsua_var.media_cfg.quality <= 4)
3694 {
3695 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
3696 }
3697 else if (pjsua_var.media_cfg.quality < 3) {
3698 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
3699 }
3700
3701 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
3702 conf_port,
Sauw Ming98766c72011-03-11 06:57:24 +00003703 param->base.clock_rate,
Benny Prijonof798e502009-03-09 13:08:16 +00003704 resample_opt,
3705 &resample_port);
3706 if (status != PJ_SUCCESS) {
3707 char errmsg[PJ_ERR_MSG_SIZE];
3708 pj_strerror(status, errmsg, sizeof(errmsg));
3709 PJ_LOG(4, (THIS_FILE,
3710 "Error creating resample port: %s",
3711 errmsg));
3712 close_snd_dev();
Benny Prijonob90fd382011-09-18 14:59:56 +00003713 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003714 }
3715
3716 conf_port = resample_port;
3717 }
3718
3719 /* Otherwise for audio switchboard, the switch's port0 setting is
3720 * derived from the sound device setting, so update the setting.
3721 */
3722 if (pjsua_var.is_mswitch) {
Nanang Izzuddin0c2aab72011-12-20 09:52:19 +00003723 if (param->base.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
3724 conf_port->info.fmt = param->base.ext_fmt;
3725 } else {
3726 unsigned bps, ptime_usec;
3727 bps = param->base.clock_rate * param->base.bits_per_sample;
3728 ptime_usec = param->base.samples_per_frame /
3729 param->base.channel_count * 1000000 /
3730 param->base.clock_rate;
3731 pjmedia_format_init_audio(&conf_port->info.fmt,
3732 PJMEDIA_FORMAT_PCM,
3733 param->base.clock_rate,
3734 param->base.channel_count,
3735 param->base.bits_per_sample,
3736 ptime_usec,
3737 bps, bps);
3738 }
Benny Prijonof798e502009-03-09 13:08:16 +00003739 }
3740
Benny Prijonoc45d9512010-12-10 11:04:30 +00003741
Benny Prijonof798e502009-03-09 13:08:16 +00003742 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00003743 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
3744 conf_port );
3745 if (status != PJ_SUCCESS) {
3746 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
3747 "sound device", status);
3748 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3749 pjsua_var.snd_port = NULL;
Benny Prijonob90fd382011-09-18 14:59:56 +00003750 goto on_error;
Benny Prijono52a93912006-08-04 20:54:37 +00003751 }
3752
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003753 /* Save the device IDs */
Sauw Ming98766c72011-03-11 06:57:24 +00003754 pjsua_var.cap_dev = param->base.rec_id;
3755 pjsua_var.play_dev = param->base.play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003756
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003757 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00003758 {
3759 pjmedia_aud_dev_info rec_info;
3760 pjmedia_aud_stream *strm;
3761 pjmedia_aud_param si;
3762 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003763
Benny Prijonof798e502009-03-09 13:08:16 +00003764 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3765 status = pjmedia_aud_stream_get_param(strm, &si);
3766 if (status == PJ_SUCCESS)
3767 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00003768
Benny Prijonof798e502009-03-09 13:08:16 +00003769 if (status==PJ_SUCCESS) {
Sauw Ming98766c72011-03-11 06:57:24 +00003770 if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) {
Benny Prijonof798e502009-03-09 13:08:16 +00003771 char tmp_buf[128];
3772 int tmp_buf_len = sizeof(tmp_buf);
3773
3774 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
3775 "%s (%dKHz)",
3776 rec_info.name,
Sauw Ming98766c72011-03-11 06:57:24 +00003777 param->base.clock_rate/1000);
Benny Prijonof798e502009-03-09 13:08:16 +00003778 pj_strset(&tmp, tmp_buf, tmp_buf_len);
3779 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
3780 } else {
3781 pjmedia_conf_set_port0_name(pjsua_var.mconf,
3782 pj_cstr(&tmp, rec_info.name));
3783 }
3784 }
3785
3786 /* Any error is not major, let it through */
3787 status = PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003788 }
Benny Prijonof798e502009-03-09 13:08:16 +00003789
3790 /* If this is the first time the audio device is open, retrieve some
3791 * settings from the device (such as volume settings) so that the
3792 * pjsua_snd_get_setting() work.
3793 */
3794 if (pjsua_var.aud_open_cnt == 0) {
3795 update_initial_aud_param();
3796 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00003797 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003798
Benny Prijonob90fd382011-09-18 14:59:56 +00003799 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003800 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003801
3802on_error:
3803 pj_log_pop_indent();
3804 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00003805}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003806
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003807
Benny Prijonof798e502009-03-09 13:08:16 +00003808/* Close existing sound device */
3809static void close_snd_dev(void)
3810{
Benny Prijonob90fd382011-09-18 14:59:56 +00003811 pj_log_push_indent();
3812
Benny Prijono2d647722011-07-13 03:05:22 +00003813 /* Notify app */
3814 if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3815 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0);
3816 }
3817
Benny Prijonof798e502009-03-09 13:08:16 +00003818 /* Close sound device */
3819 if (pjsua_var.snd_port) {
3820 pjmedia_aud_dev_info cap_info, play_info;
3821 pjmedia_aud_stream *strm;
3822 pjmedia_aud_param param;
3823
3824 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3825 pjmedia_aud_stream_get_param(strm, &param);
3826
3827 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
3828 cap_info.name[0] = '\0';
3829 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
3830 play_info.name[0] = '\0';
3831
3832 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
3833 "%s sound capture device",
3834 play_info.name, cap_info.name));
3835
3836 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
3837 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3838 pjsua_var.snd_port = NULL;
3839 }
3840
3841 /* Close null sound device */
3842 if (pjsua_var.null_snd) {
3843 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
3844 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
3845 pjsua_var.null_snd = NULL;
3846 }
3847
3848 if (pjsua_var.snd_pool)
3849 pj_pool_release(pjsua_var.snd_pool);
3850 pjsua_var.snd_pool = NULL;
Benny Prijono2d647722011-07-13 03:05:22 +00003851 pjsua_var.snd_is_on = PJ_FALSE;
Benny Prijonob90fd382011-09-18 14:59:56 +00003852
3853 pj_log_pop_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003854}
3855
3856
3857/*
3858 * Select or change sound device. Application may call this function at
3859 * any time to replace current sound device.
3860 */
3861PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
3862 int playback_dev)
3863{
3864 unsigned alt_cr_cnt = 1;
3865 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
3866 unsigned i;
3867 pj_status_t status = -1;
3868
Benny Prijonob90fd382011-09-18 14:59:56 +00003869 PJ_LOG(4,(THIS_FILE, "Set sound device: capture=%d, playback=%d",
3870 capture_dev, playback_dev));
3871 pj_log_push_indent();
3872
Benny Prijono23ea21a2009-06-03 12:43:06 +00003873 /* Null-sound */
3874 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003875 status = pjsua_set_null_snd_dev();
3876 pj_log_pop_indent();
3877 return status;
Benny Prijono23ea21a2009-06-03 12:43:06 +00003878 }
3879
Benny Prijonof798e502009-03-09 13:08:16 +00003880 /* Set default clock rate */
3881 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
3882 if (alt_cr[0] == 0)
3883 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
3884
3885 /* Allow retrying of different clock rate if we're using conference
3886 * bridge (meaning audio format is always PCM), otherwise lock on
3887 * to one clock rate.
3888 */
3889 if (pjsua_var.is_mswitch) {
3890 alt_cr_cnt = 1;
3891 } else {
3892 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
3893 }
3894
3895 /* Attempts to open the sound device with different clock rates */
3896 for (i=0; i<alt_cr_cnt; ++i) {
Sauw Ming98766c72011-03-11 06:57:24 +00003897 pjmedia_snd_port_param param;
Benny Prijonof798e502009-03-09 13:08:16 +00003898 unsigned samples_per_frame;
3899
3900 /* Create the default audio param */
3901 samples_per_frame = alt_cr[i] *
3902 pjsua_var.media_cfg.audio_frame_ptime *
3903 pjsua_var.media_cfg.channel_count / 1000;
Sauw Ming98766c72011-03-11 06:57:24 +00003904 status = create_aud_param(&param.base, capture_dev, playback_dev,
Benny Prijonof798e502009-03-09 13:08:16 +00003905 alt_cr[i], pjsua_var.media_cfg.channel_count,
3906 samples_per_frame, 16);
3907 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00003908 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003909
3910 /* Open! */
Sauw Ming98766c72011-03-11 06:57:24 +00003911 param.options = 0;
Benny Prijonof798e502009-03-09 13:08:16 +00003912 status = open_snd_dev(&param);
3913 if (status == PJ_SUCCESS)
3914 break;
3915 }
3916
3917 if (status != PJ_SUCCESS) {
3918 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003919 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003920 }
3921
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003922 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00003923 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003924
Benny Prijonob90fd382011-09-18 14:59:56 +00003925 pj_log_pop_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003926 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003927
3928on_error:
3929 pj_log_pop_indent();
3930 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003931}
3932
3933
3934/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00003935 * Get currently active sound devices. If sound devices has not been created
3936 * (for example when pjsua_start() is not called), it is possible that
3937 * the function returns PJ_SUCCESS with -1 as device IDs.
3938 */
3939PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
3940 int *playback_dev)
3941{
3942 if (capture_dev) {
3943 *capture_dev = pjsua_var.cap_dev;
3944 }
3945 if (playback_dev) {
3946 *playback_dev = pjsua_var.play_dev;
3947 }
3948
3949 return PJ_SUCCESS;
3950}
3951
3952
3953/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003954 * Use null sound device.
3955 */
3956PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
3957{
3958 pjmedia_port *conf_port;
3959 pj_status_t status;
3960
Benny Prijonob90fd382011-09-18 14:59:56 +00003961 PJ_LOG(4,(THIS_FILE, "Setting null sound device.."));
3962 pj_log_push_indent();
3963
3964
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003965 /* Close existing sound device */
3966 close_snd_dev();
3967
Benny Prijono2d647722011-07-13 03:05:22 +00003968 /* Notify app */
3969 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3970 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
3971 }
3972
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003973 /* Create memory pool for sound device. */
3974 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3975 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3976
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003977 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
3978
3979 /* Get the port0 of the conference bridge. */
3980 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3981 pj_assert(conf_port != NULL);
3982
3983 /* Create master port, connecting port0 of the conference bridge to
3984 * a null port.
3985 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003986 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003987 conf_port, 0, &pjsua_var.null_snd);
3988 if (status != PJ_SUCCESS) {
3989 pjsua_perror(THIS_FILE, "Unable to create null sound device",
3990 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003991 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003992 return status;
3993 }
3994
3995 /* Start the master port */
3996 status = pjmedia_master_port_start(pjsua_var.null_snd);
3997 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
3998
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003999 pjsua_var.cap_dev = NULL_SND_DEV_ID;
4000 pjsua_var.play_dev = NULL_SND_DEV_ID;
4001
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00004002 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00004003 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00004004
Benny Prijonob90fd382011-09-18 14:59:56 +00004005 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004006 return PJ_SUCCESS;
4007}
4008
4009
Benny Prijonoe909eac2006-07-27 22:04:56 +00004010
4011/*
4012 * Use no device!
4013 */
4014PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
4015{
4016 /* Close existing sound device */
4017 close_snd_dev();
4018
4019 pjsua_var.no_snd = PJ_TRUE;
4020 return pjmedia_conf_get_master_port(pjsua_var.mconf);
4021}
4022
4023
Benny Prijonof20687a2006-08-04 18:27:19 +00004024/*
4025 * Configure the AEC settings of the sound port.
4026 */
Benny Prijono5da50432006-08-07 10:24:52 +00004027PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00004028{
4029 pjsua_var.media_cfg.ec_tail_len = tail_ms;
4030
4031 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00004032 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
4033 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00004034
4035 return PJ_SUCCESS;
4036}
4037
4038
4039/*
4040 * Get current AEC tail length.
4041 */
Benny Prijono22dfe592006-08-06 12:07:13 +00004042PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00004043{
4044 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
4045 return PJ_SUCCESS;
4046}
4047
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00004048
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00004049/*
Benny Prijonof798e502009-03-09 13:08:16 +00004050 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00004051 */
Benny Prijonof798e502009-03-09 13:08:16 +00004052PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00004053{
Benny Prijonof798e502009-03-09 13:08:16 +00004054 return pjsua_var.snd_port != NULL;
4055}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00004056
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00004057
Benny Prijonof798e502009-03-09 13:08:16 +00004058/*
4059 * Configure sound device setting to the sound device being used.
4060 */
4061PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
4062 const void *pval,
4063 pj_bool_t keep)
4064{
Benny Prijono09b0ff62009-03-10 12:07:51 +00004065 pj_status_t status;
4066
Benny Prijonof798e502009-03-09 13:08:16 +00004067 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00004068 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00004069 return PJMEDIA_EAUD_INVCAP;
4070 }
4071
Benny Prijono09b0ff62009-03-10 12:07:51 +00004072 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00004073 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00004074 pjmedia_aud_stream *strm;
4075
4076 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00004077 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00004078 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00004079 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00004080 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00004081
4082 if (status != PJ_SUCCESS)
4083 return status;
4084
4085 /* Save in internal param for later device open */
4086 if (keep) {
4087 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
4088 cap, pval);
4089 }
4090
4091 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00004092}
4093
4094/*
4095 * Retrieve a sound device setting.
4096 */
4097PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
4098 void *pval)
4099{
4100 /* If sound device has never been opened before, open it to
4101 * retrieve the initial setting from the device (e.g. audio
4102 * volume)
4103 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00004104 if (pjsua_var.aud_open_cnt==0) {
4105 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00004106 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00004107 close_snd_dev();
4108 }
Benny Prijonof798e502009-03-09 13:08:16 +00004109
4110 if (pjsua_snd_is_active()) {
4111 /* Sound is active, retrieve from device directly */
4112 pjmedia_aud_stream *strm;
4113
4114 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
4115 return pjmedia_aud_stream_get_cap(strm, cap, pval);
4116 } else {
4117 /* Otherwise retrieve from internal param */
4118 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
4119 cap, pval);
4120 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00004121}
4122
4123
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004124/*****************************************************************************
4125 * Codecs.
4126 */
4127
4128/*
4129 * Enum all supported codecs in the system.
4130 */
4131PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
4132 unsigned *p_count )
4133{
4134 pjmedia_codec_mgr *codec_mgr;
4135 pjmedia_codec_info info[32];
4136 unsigned i, count, prio[32];
4137 pj_status_t status;
4138
4139 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4140 count = PJ_ARRAY_SIZE(info);
4141 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
4142 if (status != PJ_SUCCESS) {
4143 *p_count = 0;
4144 return status;
4145 }
4146
4147 if (count > *p_count) count = *p_count;
4148
4149 for (i=0; i<count; ++i) {
Nanang Izzuddin56b2ce42011-04-06 13:55:01 +00004150 pj_bzero(&id[i], sizeof(pjsua_codec_info));
4151
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004152 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
4153 id[i].codec_id = pj_str(id[i].buf_);
4154 id[i].priority = (pj_uint8_t) prio[i];
4155 }
4156
4157 *p_count = count;
4158
4159 return PJ_SUCCESS;
4160}
4161
4162
4163/*
4164 * Change codec priority.
4165 */
4166PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
4167 pj_uint8_t priority )
4168{
Benny Prijono88accae2008-06-26 15:48:14 +00004169 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004170 pjmedia_codec_mgr *codec_mgr;
4171
4172 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4173
Benny Prijono88accae2008-06-26 15:48:14 +00004174 if (codec_id->slen==1 && *codec_id->ptr=='*')
4175 codec_id = &all;
4176
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004177 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
4178 priority);
4179}
4180
4181
4182/*
4183 * Get codec parameters.
4184 */
4185PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
4186 pjmedia_codec_param *param )
4187{
Benny Prijono88accae2008-06-26 15:48:14 +00004188 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004189 const pjmedia_codec_info *info;
4190 pjmedia_codec_mgr *codec_mgr;
4191 unsigned count = 1;
4192 pj_status_t status;
4193
4194 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4195
Benny Prijono88accae2008-06-26 15:48:14 +00004196 if (codec_id->slen==1 && *codec_id->ptr=='*')
4197 codec_id = &all;
4198
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004199 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
4200 &count, &info, NULL);
4201 if (status != PJ_SUCCESS)
4202 return status;
4203
4204 if (count != 1)
Nanang Izzuddin50fae732011-03-22 09:49:23 +00004205 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004206
4207 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
4208 return status;
4209}
4210
4211
4212/*
4213 * Set codec parameters.
4214 */
Nanang Izzuddin06839e72010-01-27 11:48:31 +00004215PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004216 const pjmedia_codec_param *param)
4217{
Nanang Izzuddin06839e72010-01-27 11:48:31 +00004218 const pjmedia_codec_info *info[2];
4219 pjmedia_codec_mgr *codec_mgr;
4220 unsigned count = 2;
4221 pj_status_t status;
4222
4223 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4224
4225 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
4226 &count, info, NULL);
4227 if (status != PJ_SUCCESS)
4228 return status;
4229
4230 /* Codec ID should be specific, except for G.722.1 */
4231 if (count > 1 &&
4232 pj_strnicmp2(codec_id, "G7221/16", 8) != 0 &&
4233 pj_strnicmp2(codec_id, "G7221/32", 8) != 0)
4234 {
4235 pj_assert(!"Codec ID is not specific");
4236 return PJ_ETOOMANY;
4237 }
4238
4239 status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param);
4240 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004241}
Nanang Izzuddin50fae732011-03-22 09:49:23 +00004242
4243
Nanang Izzuddin3da4ad82011-12-07 10:43:28 +00004244pj_status_t pjsua_media_apply_xml_control(pjsua_call_id call_id,
4245 const pj_str_t *xml_st)
4246{
4247 pjsua_call *call = &pjsua_var.calls[call_id];
4248 const pj_str_t PICT_FAST_UPDATE = {"picture_fast_update", 19};
4249
4250#if PJMEDIA_HAS_VIDEO
4251 if (pj_strstr(xml_st, &PICT_FAST_UPDATE)) {
4252 unsigned i;
4253
4254 PJ_LOG(4,(THIS_FILE, "Received keyframe request via SIP INFO"));
4255
4256 for (i = 0; i < call->med_cnt; ++i) {
4257 pjsua_call_media *cm = &call->media[i];
4258 if (cm->type != PJMEDIA_TYPE_VIDEO || !cm->strm.v.stream)
4259 continue;
4260
4261 pjmedia_vid_stream_send_keyframe(cm->strm.v.stream);
4262 }
4263
4264 return PJ_SUCCESS;
4265 }
4266#endif
4267
4268 /* Just to avoid compiler warning of unused var */
4269 PJ_UNUSED_ARG(xml_st);
4270
4271 return PJ_ENOTSUP;
4272}
4273