blob: e7743659d4478b0afd24979e625f222ef988fb44 [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
Nanang Izzuddina62ffc92011-05-05 06:14:19 +00003 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
Benny Prijono32177c02008-06-20 22:44:47 +00004 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
Benny Prijonoeebe9af2006-06-13 22:57:13 +00005 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20#include <pjsua-lib/pjsua.h>
21#include <pjsua-lib/pjsua_internal.h>
22
23
24#define THIS_FILE "pjsua_media.c"
25
Nanang Izzuddin68559c32008-06-13 17:01:46 +000026#define DEFAULT_RTP_PORT 4000
27
28#define NULL_SND_DEV_ID -99
Benny Prijono80eee892007-11-03 22:43:23 +000029
30#ifndef PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
31# define PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT 0
32#endif
33
Benny Prijonoeebe9af2006-06-13 22:57:13 +000034
Benny Prijonode479562007-03-15 10:23:55 +000035/* Next RTP port to be used */
36static pj_uint16_t next_rtp_port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000037
Benny Prijonof798e502009-03-09 13:08:16 +000038/* Open sound dev */
Sauw Ming98766c72011-03-11 06:57:24 +000039static pj_status_t open_snd_dev(pjmedia_snd_port_param *param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000040/* Close existing sound device */
41static void close_snd_dev(void);
Benny Prijonof798e502009-03-09 13:08:16 +000042/* Create audio device param */
43static pj_status_t create_aud_param(pjmedia_aud_param *param,
44 pjmedia_aud_dev_index capture_dev,
45 pjmedia_aud_dev_index playback_dev,
46 unsigned clock_rate,
47 unsigned channel_count,
48 unsigned samples_per_frame,
49 unsigned bits_per_sample);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000050
51
Benny Prijonof76e1392008-06-06 14:51:48 +000052static void pjsua_media_config_dup(pj_pool_t *pool,
53 pjsua_media_config *dst,
54 const pjsua_media_config *src)
55{
56 pj_memcpy(dst, src, sizeof(*src));
57 pj_strdup(pool, &dst->turn_server, &src->turn_server);
58 pj_stun_auth_cred_dup(pool, &dst->turn_auth_cred, &src->turn_auth_cred);
59}
60
Nanang Izzuddin50fae732011-03-22 09:49:23 +000061
Benny Prijonoeebe9af2006-06-13 22:57:13 +000062/**
63 * Init media subsystems.
64 */
65pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
66{
Benny Prijonoba5926a2007-05-02 11:29:37 +000067 pj_str_t codec_id = {NULL, 0};
Benny Prijono0498d902006-06-19 14:49:14 +000068 unsigned opt;
Benny Prijono35fc1eb2011-07-15 09:51:46 +000069 pjmedia_audio_codec_config codec_cfg;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000070 pj_status_t status;
71
Benny Prijonofc24e692007-01-27 18:31:51 +000072 /* To suppress warning about unused var when all codecs are disabled */
73 PJ_UNUSED_ARG(codec_id);
74
Benny Prijonob90fd382011-09-18 14:59:56 +000075 pj_log_push_indent();
76
Benny Prijonof798e502009-03-09 13:08:16 +000077 /* Specify which audio device settings are save-able */
78 pjsua_var.aud_svmask = 0xFFFFFFFF;
79 /* These are not-settable */
80 pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
81 PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER |
82 PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER);
Benny Prijonoe506c8c2009-03-10 13:28:43 +000083 /* EC settings use different API */
84 pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EC |
85 PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijonof798e502009-03-09 13:08:16 +000086
Benny Prijonoeebe9af2006-06-13 22:57:13 +000087 /* Copy configuration */
Benny Prijonof76e1392008-06-06 14:51:48 +000088 pjsua_media_config_dup(pjsua_var.pool, &pjsua_var.media_cfg, cfg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +000089
90 /* Normalize configuration */
Benny Prijono50f19b32008-03-11 13:15:43 +000091 if (pjsua_var.media_cfg.snd_clock_rate == 0) {
92 pjsua_var.media_cfg.snd_clock_rate = pjsua_var.media_cfg.clock_rate;
93 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +000094
95 if (pjsua_var.media_cfg.has_ioqueue &&
96 pjsua_var.media_cfg.thread_cnt == 0)
97 {
98 pjsua_var.media_cfg.thread_cnt = 1;
99 }
100
101 if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
102 pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
103 }
104
105 /* Create media endpoint. */
106 status = pjmedia_endpt_create(&pjsua_var.cp.factory,
107 pjsua_var.media_cfg.has_ioqueue? NULL :
108 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
109 pjsua_var.media_cfg.thread_cnt,
110 &pjsua_var.med_endpt);
111 if (status != PJ_SUCCESS) {
112 pjsua_perror(THIS_FILE,
113 "Media stack initialization has returned error",
114 status);
Benny Prijonob90fd382011-09-18 14:59:56 +0000115 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000116 }
117
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000118 /*
119 * Register all codecs
120 */
121 pjmedia_audio_codec_config_default(&codec_cfg);
122 codec_cfg.speex.quality = pjsua_var.media_cfg.quality;
123 codec_cfg.speex.complexity = -1;
124 codec_cfg.ilbc.mode = pjsua_var.media_cfg.ilbc_mode;
Nanang Izzuddin493a8db2008-08-15 13:17:39 +0000125
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000126#if PJMEDIA_HAS_PASSTHROUGH_CODECS
127 /* Register passthrough codecs */
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000128 {
129 unsigned aud_idx;
130 unsigned ext_fmt_cnt = 0;
131 pjmedia_format ext_fmts[32];
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000132
133 /* List extended formats supported by audio devices */
134 for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) {
135 pjmedia_aud_dev_info aud_info;
136 unsigned i;
137
138 status = pjmedia_aud_dev_get_info(aud_idx, &aud_info);
139 if (status != PJ_SUCCESS) {
140 pjsua_perror(THIS_FILE, "Error querying audio device info",
141 status);
Benny Prijonob90fd382011-09-18 14:59:56 +0000142 goto on_error;
Nanang Izzuddinabf58db2009-06-30 15:02:06 +0000143 }
144
145 /* Collect extended formats supported by this audio device */
146 for (i = 0; i < aud_info.ext_fmt_cnt; ++i) {
147 unsigned j;
148 pj_bool_t is_listed = PJ_FALSE;
149
150 /* See if this extended format is already in the list */
151 for (j = 0; j < ext_fmt_cnt && !is_listed; ++j) {
152 if (ext_fmts[j].id == aud_info.ext_fmt[i].id &&
153 ext_fmts[j].bitrate == aud_info.ext_fmt[i].bitrate)
154 {
155 is_listed = PJ_TRUE;
156 }
157 }
158
159 /* Put this format into the list, if it is not in the list */
160 if (!is_listed)
161 ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i];
162
163 pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts));
164 }
165 }
166
167 /* Init the passthrough codec with supported formats only */
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000168 codec_cfg.passthrough.setting.fmt_cnt = ext_fmt_cnt;
169 codec_cfg.passthrough.setting.fmts = ext_fmts;
170 codec_cfg.passthrough.setting.ilbc_mode = cfg->ilbc_mode;
Nanang Izzuddin81db8c72009-02-05 10:59:14 +0000171 }
172#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
173
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000174 /* Register all codecs */
175 status = pjmedia_codec_register_audio_codecs(pjsua_var.med_endpt,
176 &codec_cfg);
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000177 if (status != PJ_SUCCESS) {
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000178 PJ_PERROR(1,(THIS_FILE, status, "Error registering codecs"));
Benny Prijonob90fd382011-09-18 14:59:56 +0000179 goto on_error;
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000180 }
Nanang Izzuddin57b88572009-04-01 12:05:34 +0000181
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000182 /* Set speex/16000 to higher priority*/
183 codec_id = pj_str("speex/16000");
184 pjmedia_codec_mgr_set_codec_priority(
185 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
186 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
187
188 /* Set speex/8000 to next higher priority*/
189 codec_id = pj_str("speex/8000");
190 pjmedia_codec_mgr_set_codec_priority(
191 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
192 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000193
194 /* Disable ALL L16 codecs */
195 codec_id = pj_str("L16");
196 pjmedia_codec_mgr_set_codec_priority(
197 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
198 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
199
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000200
201 /* Save additional conference bridge parameters for future
202 * reference.
203 */
Benny Prijono7d60d052008-03-29 12:24:20 +0000204 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000205 pjsua_var.mconf_cfg.bits_per_sample = 16;
Benny Prijono6e7c5ad2008-03-13 10:15:16 +0000206 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
207 pjsua_var.mconf_cfg.channel_count *
208 pjsua_var.media_cfg.audio_frame_ptime /
209 1000;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000210
Benny Prijono0498d902006-06-19 14:49:14 +0000211 /* Init options for conference bridge. */
212 opt = PJMEDIA_CONF_NO_DEVICE;
213 if (pjsua_var.media_cfg.quality >= 3 &&
Benny Prijono7ca96da2006-08-07 12:11:40 +0000214 pjsua_var.media_cfg.quality <= 4)
Benny Prijono0498d902006-06-19 14:49:14 +0000215 {
216 opt |= PJMEDIA_CONF_SMALL_FILTER;
217 }
218 else if (pjsua_var.media_cfg.quality < 3) {
219 opt |= PJMEDIA_CONF_USE_LINEAR;
220 }
221
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000222 /* Init conference bridge. */
223 status = pjmedia_conf_create(pjsua_var.pool,
224 pjsua_var.media_cfg.max_media_ports,
225 pjsua_var.media_cfg.clock_rate,
226 pjsua_var.mconf_cfg.channel_count,
227 pjsua_var.mconf_cfg.samples_per_frame,
228 pjsua_var.mconf_cfg.bits_per_sample,
Benny Prijono0498d902006-06-19 14:49:14 +0000229 opt, &pjsua_var.mconf);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000230 if (status != PJ_SUCCESS) {
Benny Prijono50f19b32008-03-11 13:15:43 +0000231 pjsua_perror(THIS_FILE, "Error creating conference bridge",
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000232 status);
Benny Prijonob90fd382011-09-18 14:59:56 +0000233 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000234 }
235
Benny Prijonof798e502009-03-09 13:08:16 +0000236 /* Are we using the audio switchboard (a.k.a APS-Direct)? */
237 pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
238 ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
239
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000240 /* Create null port just in case user wants to use null sound. */
241 status = pjmedia_null_port_create(pjsua_var.pool,
242 pjsua_var.media_cfg.clock_rate,
243 pjsua_var.mconf_cfg.channel_count,
244 pjsua_var.mconf_cfg.samples_per_frame,
245 pjsua_var.mconf_cfg.bits_per_sample,
246 &pjsua_var.null_port);
247 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
248
Nanang Izzuddin69b69ae2009-04-14 15:18:30 +0000249#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
250 /* Initialize SRTP library. */
251 status = pjmedia_srtp_init_lib();
252 if (status != PJ_SUCCESS) {
253 pjsua_perror(THIS_FILE, "Error initializing SRTP library",
254 status);
Benny Prijonob90fd382011-09-18 14:59:56 +0000255 goto on_error;
Nanang Izzuddin69b69ae2009-04-14 15:18:30 +0000256 }
257#endif
258
Benny Prijono9f468d12011-07-07 07:46:33 +0000259 /* Video */
260#if PJMEDIA_HAS_VIDEO
261 status = pjsua_vid_subsys_init();
262 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +0000263 goto on_error;
Benny Prijono9f468d12011-07-07 07:46:33 +0000264#endif
265
Benny Prijonob90fd382011-09-18 14:59:56 +0000266 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000267 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +0000268
269on_error:
270 pj_log_pop_indent();
271 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000272}
273
274
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000275/* Check if sound device is idle. */
276static void check_snd_dev_idle()
277{
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000278 unsigned call_cnt;
279
280 /* Get the call count, we shouldn't close the sound device when there is
281 * any calls active.
282 */
283 call_cnt = pjsua_call_get_count();
284
285 /* When this function is called from pjsua_media_channel_deinit() upon
286 * disconnecting call, actually the call count hasn't been updated/
287 * decreased. So we put additional check here, if there is only one
288 * call and it's in DISCONNECTED state, there is actually no active
289 * call.
290 */
291 if (call_cnt == 1) {
292 pjsua_call_id call_id;
293 pj_status_t status;
294
295 status = pjsua_enum_calls(&call_id, &call_cnt);
296 if (status == PJ_SUCCESS && call_cnt > 0 &&
297 !pjsua_call_is_active(call_id))
298 {
299 call_cnt = 0;
300 }
301 }
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000302
303 /* Activate sound device auto-close timer if sound device is idle.
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000304 * It is idle when there is no port connection in the bridge and
305 * there is no active call.
Benny Prijono2d647722011-07-13 03:05:22 +0000306 *
307 * Note: this block is now valid if no snd dev is used because of #1299
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000308 */
Benny Prijono2d647722011-07-13 03:05:22 +0000309 if ((pjsua_var.snd_port!=NULL || pjsua_var.null_snd!=NULL ||
310 pjsua_var.no_snd) &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000311 pjsua_var.snd_idle_timer.id == PJ_FALSE &&
312 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0 &&
Nanang Izzuddin7082b262009-06-12 11:15:08 +0000313 call_cnt == 0 &&
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000314 pjsua_var.media_cfg.snd_auto_close_time >= 0)
315 {
316 pj_time_val delay;
317
318 delay.msec = 0;
319 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
320
321 pjsua_var.snd_idle_timer.id = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000322 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
Nanang Izzuddin148fd392008-06-16 09:52:50 +0000323 &delay);
324 }
325}
326
327
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000328/* Timer callback to close sound device */
329static void close_snd_timer_cb( pj_timer_heap_t *th,
330 pj_timer_entry *entry)
331{
332 PJ_UNUSED_ARG(th);
333
Benny Prijono0f711b42009-05-06 19:08:43 +0000334 PJSUA_LOCK();
335 if (entry->id) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000336 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",
Benny Prijono0f711b42009-05-06 19:08:43 +0000337 pjsua_var.media_cfg.snd_auto_close_time));
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000338
Benny Prijono0f711b42009-05-06 19:08:43 +0000339 entry->id = PJ_FALSE;
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000340
Benny Prijono0f711b42009-05-06 19:08:43 +0000341 close_snd_dev();
342 }
343 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000344}
345
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000346
347/*
348 * Start pjsua media subsystem.
349 */
350pj_status_t pjsua_media_subsys_start(void)
351{
352 pj_status_t status;
353
Benny Prijonob90fd382011-09-18 14:59:56 +0000354 pj_log_push_indent();
355
Benny Prijono0bc99a92011-03-17 04:34:43 +0000356#if DISABLED_FOR_TICKET_1185
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000357 /* Create media for calls, if none is specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000358 if (pjsua_var.calls[0].media[0].tp == NULL) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000359 pjsua_transport_config transport_cfg;
360
361 /* Create default transport config */
362 pjsua_transport_config_default(&transport_cfg);
363 transport_cfg.port = DEFAULT_RTP_PORT;
364
365 status = pjsua_media_transports_create(&transport_cfg);
Benny Prijonob90fd382011-09-18 14:59:56 +0000366 if (status != PJ_SUCCESS) {
367 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000368 return status;
Benny Prijonob90fd382011-09-18 14:59:56 +0000369 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000370 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000371#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000372
Benny Prijono0bc99a92011-03-17 04:34:43 +0000373 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
Nanang Izzuddin68559c32008-06-13 17:01:46 +0000374 &close_snd_timer_cb);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000375
Benny Prijono9f468d12011-07-07 07:46:33 +0000376 /* Video */
377#if PJMEDIA_HAS_VIDEO
378 status = pjsua_vid_subsys_start();
Benny Prijonob90fd382011-09-18 14:59:56 +0000379 if (status != PJ_SUCCESS) {
380 pj_log_pop_indent();
Benny Prijono9f468d12011-07-07 07:46:33 +0000381 return status;
Benny Prijonob90fd382011-09-18 14:59:56 +0000382 }
Benny Prijono9f468d12011-07-07 07:46:33 +0000383#endif
384
Benny Prijonobf53b002010-01-04 13:08:31 +0000385 /* Perform NAT detection */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000386 status = pjsua_detect_nat_type();
387 if (status != PJ_SUCCESS) {
388 PJ_PERROR(1,(THIS_FILE, status, "NAT type detection failed"));
389 }
Benny Prijonobf53b002010-01-04 13:08:31 +0000390
Benny Prijonob90fd382011-09-18 14:59:56 +0000391 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000392 return PJ_SUCCESS;
393}
394
395
396/*
397 * Destroy pjsua media subsystem.
398 */
399pj_status_t pjsua_media_subsys_destroy(void)
400{
401 unsigned i;
402
Benny Prijono384dab42009-10-14 01:58:04 +0000403 PJ_LOG(4,(THIS_FILE, "Shutting down media.."));
Benny Prijonob90fd382011-09-18 14:59:56 +0000404 pj_log_push_indent();
Benny Prijono384dab42009-10-14 01:58:04 +0000405
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000406 close_snd_dev();
407
408 if (pjsua_var.mconf) {
409 pjmedia_conf_destroy(pjsua_var.mconf);
410 pjsua_var.mconf = NULL;
411 }
412
413 if (pjsua_var.null_port) {
414 pjmedia_port_destroy(pjsua_var.null_port);
415 pjsua_var.null_port = NULL;
416 }
417
418 /* Destroy file players */
419 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
420 if (pjsua_var.player[i].port) {
421 pjmedia_port_destroy(pjsua_var.player[i].port);
422 pjsua_var.player[i].port = NULL;
423 }
424 }
425
426 /* Destroy file recorders */
427 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
428 if (pjsua_var.recorder[i].port) {
429 pjmedia_port_destroy(pjsua_var.recorder[i].port);
430 pjsua_var.recorder[i].port = NULL;
431 }
432 }
433
434 /* Close media transports */
Benny Prijono0875ae82006-12-26 00:11:48 +0000435 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000436 unsigned strm_idx;
437 pjsua_call *call = &pjsua_var.calls[i];
438 for (strm_idx=0; strm_idx<call->med_cnt; ++strm_idx) {
439 pjsua_call_media *call_med = &call->media[strm_idx];
440 if (call_med->tp_st != PJSUA_MED_TP_IDLE) {
441 pjsua_media_channel_deinit(i);
442 }
443 if (call_med->tp && call_med->tp_auto_del) {
444 pjmedia_transport_close(call_med->tp);
445 }
446 call_med->tp = NULL;
Benny Prijono311b63f2008-07-14 11:31:40 +0000447 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000448 }
449
450 /* Destroy media endpoint. */
451 if (pjsua_var.med_endpt) {
452
Benny Prijono0bc99a92011-03-17 04:34:43 +0000453# if PJMEDIA_HAS_VIDEO
Benny Prijono9f468d12011-07-07 07:46:33 +0000454 pjsua_vid_subsys_destroy();
Benny Prijono0bc99a92011-03-17 04:34:43 +0000455# endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000456
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000457 pjmedia_endpt_destroy(pjsua_var.med_endpt);
458 pjsua_var.med_endpt = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000459
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000460 /* Deinitialize sound subsystem */
Benny Prijonoa41d66e2007-10-01 12:17:23 +0000461 // Not necessary, as pjmedia_snd_deinit() should have been called
462 // in pjmedia_endpt_destroy().
463 //pjmedia_snd_deinit();
Benny Prijonoad2e0ca2007-04-29 12:31:51 +0000464 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000465
Benny Prijonode479562007-03-15 10:23:55 +0000466 /* Reset RTP port */
467 next_rtp_port = 0;
468
Benny Prijonob90fd382011-09-18 14:59:56 +0000469 pj_log_pop_indent();
470
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000471 return PJ_SUCCESS;
472}
473
Benny Prijono0bc99a92011-03-17 04:34:43 +0000474/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000475 * Create RTP and RTCP socket pair, and possibly resolve their public
476 * address via STUN.
477 */
478static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
479 pjmedia_sock_info *skinfo)
480{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000481 enum {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000482 RTP_RETRY = 100
483 };
484 int i;
485 pj_sockaddr_in bound_addr;
486 pj_sockaddr_in mapped_addr[2];
487 pj_status_t status = PJ_SUCCESS;
488 char addr_buf[PJ_INET6_ADDRSTRLEN+2];
489 pj_sock_t sock[2];
490
491 /* Make sure STUN server resolution has completed */
492 status = resolve_stun_server(PJ_TRUE);
493 if (status != PJ_SUCCESS) {
494 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
495 return status;
496 }
497
498 if (next_rtp_port == 0)
499 next_rtp_port = (pj_uint16_t)cfg->port;
500
Benny Prijono0bc99a92011-03-17 04:34:43 +0000501 if (next_rtp_port == 0)
502 next_rtp_port = (pj_uint16_t)40000;
503
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000504 for (i=0; i<2; ++i)
505 sock[i] = PJ_INVALID_SOCKET;
506
507 bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
508 if (cfg->bound_addr.slen) {
509 status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
510 if (status != PJ_SUCCESS) {
511 pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
512 status);
513 return status;
514 }
515 }
516
517 /* Loop retry to bind RTP and RTCP sockets. */
518 for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
519
520 /* Create RTP socket. */
521 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
522 if (status != PJ_SUCCESS) {
523 pjsua_perror(THIS_FILE, "socket() error", status);
524 return status;
525 }
526
527 /* Apply QoS to RTP socket, if specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000528 status = pj_sock_apply_qos2(sock[0], cfg->qos_type,
529 &cfg->qos_params,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000530 2, THIS_FILE, "RTP socket");
531
532 /* Bind RTP socket */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000533 status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000534 next_rtp_port);
535 if (status != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000536 pj_sock_close(sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000537 sock[0] = PJ_INVALID_SOCKET;
538 continue;
539 }
540
541 /* Create RTCP socket. */
542 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
543 if (status != PJ_SUCCESS) {
544 pjsua_perror(THIS_FILE, "socket() error", status);
545 pj_sock_close(sock[0]);
546 return status;
547 }
548
549 /* Apply QoS to RTCP socket, if specified */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000550 status = pj_sock_apply_qos2(sock[1], cfg->qos_type,
551 &cfg->qos_params,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000552 2, THIS_FILE, "RTCP socket");
553
554 /* Bind RTCP socket */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000555 status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000556 (pj_uint16_t)(next_rtp_port+1));
557 if (status != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000558 pj_sock_close(sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000559 sock[0] = PJ_INVALID_SOCKET;
560
Benny Prijono0bc99a92011-03-17 04:34:43 +0000561 pj_sock_close(sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000562 sock[1] = PJ_INVALID_SOCKET;
563 continue;
564 }
565
566 /*
567 * If we're configured to use STUN, then find out the mapped address,
568 * and make sure that the mapped RTCP port is adjacent with the RTP.
569 */
570 if (pjsua_var.stun_srv.addr.sa_family != 0) {
571 char ip_addr[32];
572 pj_str_t stun_srv;
573
Benny Prijono0bc99a92011-03-17 04:34:43 +0000574 pj_ansi_strcpy(ip_addr,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000575 pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
576 stun_srv = pj_str(ip_addr);
577
578 status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
579 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
580 &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
581 mapped_addr);
582 if (status != PJ_SUCCESS) {
583 pjsua_perror(THIS_FILE, "STUN resolve error", status);
584 goto on_error;
585 }
586
587#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
Benny Prijono0bc99a92011-03-17 04:34:43 +0000588 if (pj_ntohs(mapped_addr[1].sin_port) ==
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000589 pj_ntohs(mapped_addr[0].sin_port)+1)
590 {
591 /* Success! */
592 break;
593 }
594
Benny Prijono0bc99a92011-03-17 04:34:43 +0000595 pj_sock_close(sock[0]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000596 sock[0] = PJ_INVALID_SOCKET;
597
Benny Prijono0bc99a92011-03-17 04:34:43 +0000598 pj_sock_close(sock[1]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000599 sock[1] = PJ_INVALID_SOCKET;
600#else
Benny Prijono0bc99a92011-03-17 04:34:43 +0000601 if (pj_ntohs(mapped_addr[1].sin_port) !=
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000602 pj_ntohs(mapped_addr[0].sin_port)+1)
603 {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000604 PJ_LOG(4,(THIS_FILE,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000605 "Note: STUN mapped RTCP port %d is not adjacent"
606 " to RTP port %d",
607 pj_ntohs(mapped_addr[1].sin_port),
608 pj_ntohs(mapped_addr[0].sin_port)));
609 }
610 /* Success! */
611 break;
612#endif
613
614 } else if (cfg->public_addr.slen) {
615
616 status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
617 (pj_uint16_t)next_rtp_port);
618 if (status != PJ_SUCCESS)
619 goto on_error;
620
621 status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
622 (pj_uint16_t)(next_rtp_port+1));
623 if (status != PJ_SUCCESS)
624 goto on_error;
625
626 break;
627
628 } else {
629
630 if (bound_addr.sin_addr.s_addr == 0) {
631 pj_sockaddr addr;
632
633 /* Get local IP address. */
634 status = pj_gethostip(pj_AF_INET(), &addr);
635 if (status != PJ_SUCCESS)
636 goto on_error;
637
638 bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
639 }
640
641 for (i=0; i<2; ++i) {
642 pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
643 mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
644 }
645
646 mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
647 mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
648 break;
649 }
650 }
651
652 if (sock[0] == PJ_INVALID_SOCKET) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000653 PJ_LOG(1,(THIS_FILE,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000654 "Unable to find appropriate RTP/RTCP ports combination"));
655 goto on_error;
656 }
657
658
659 skinfo->rtp_sock = sock[0];
Benny Prijono0bc99a92011-03-17 04:34:43 +0000660 pj_memcpy(&skinfo->rtp_addr_name,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000661 &mapped_addr[0], sizeof(pj_sockaddr_in));
662
663 skinfo->rtcp_sock = sock[1];
Benny Prijono0bc99a92011-03-17 04:34:43 +0000664 pj_memcpy(&skinfo->rtcp_addr_name,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000665 &mapped_addr[1], sizeof(pj_sockaddr_in));
666
667 PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
668 pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
669 sizeof(addr_buf), 3)));
670 PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
671 pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
672 sizeof(addr_buf), 3)));
673
674 next_rtp_port += 2;
675 return PJ_SUCCESS;
676
677on_error:
678 for (i=0; i<2; ++i) {
679 if (sock[i] != PJ_INVALID_SOCKET)
680 pj_sock_close(sock[i]);
681 }
682 return status;
683}
684
Benny Prijono0bc99a92011-03-17 04:34:43 +0000685/* Create normal UDP media transports */
686static pj_status_t create_udp_media_transport(const pjsua_transport_config *cfg,
687 pjsua_call_media *call_med)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000688{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000689 pjmedia_sock_info skinfo;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000690 pj_status_t status;
691
Benny Prijono0bc99a92011-03-17 04:34:43 +0000692 status = create_rtp_rtcp_sock(cfg, &skinfo);
693 if (status != PJ_SUCCESS) {
694 pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
695 status);
696 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000697 }
698
Benny Prijono0bc99a92011-03-17 04:34:43 +0000699 status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
700 &skinfo, 0, &call_med->tp);
701 if (status != PJ_SUCCESS) {
702 pjsua_perror(THIS_FILE, "Unable to create media transport",
703 status);
704 goto on_error;
705 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000706
Benny Prijono0bc99a92011-03-17 04:34:43 +0000707 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
708 pjsua_var.media_cfg.tx_drop_pct);
709
710 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
711 pjsua_var.media_cfg.rx_drop_pct);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000712
Sauw Ming73ecfe82011-09-21 10:20:01 +0000713 call_med->tp_ready = PJ_SUCCESS;
714
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000715 return PJ_SUCCESS;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000716
717on_error:
718 if (call_med->tp)
719 pjmedia_transport_close(call_med->tp);
720
721 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000722}
723
Benny Prijono0bc99a92011-03-17 04:34:43 +0000724#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +0000725/* Create normal UDP media transports */
726static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000727{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000728 unsigned i;
729 pj_status_t status;
730
Benny Prijono0bc99a92011-03-17 04:34:43 +0000731 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
732 pjsua_call *call = &pjsua_var.calls[i];
733 unsigned strm_idx;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000734
Benny Prijono0bc99a92011-03-17 04:34:43 +0000735 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
736 pjsua_call_media *call_med = &call->media[strm_idx];
737
738 status = create_udp_media_transport(cfg, &call_med->tp);
739 if (status != PJ_SUCCESS)
740 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000741 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000742 }
743
Benny Prijonoc97608e2007-03-23 16:34:20 +0000744 return PJ_SUCCESS;
745
746on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000747 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
748 pjsua_call *call = &pjsua_var.calls[i];
749 unsigned strm_idx;
750
751 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
752 pjsua_call_media *call_med = &call->media[strm_idx];
753
754 if (call_med->tp) {
755 pjmedia_transport_close(call_med->tp);
756 call_med->tp = NULL;
757 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000758 }
759 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000760 return status;
761}
Benny Prijono0bc99a92011-03-17 04:34:43 +0000762#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000763
Benny Prijono096c56c2007-09-15 08:30:16 +0000764/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000765static void on_ice_complete(pjmedia_transport *tp,
766 pj_ice_strans_op op,
767 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000768{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000769 pjsua_call_media *call_med = (pjsua_call_media*)tp->user_data;
Benny Prijono096c56c2007-09-15 08:30:16 +0000770
Benny Prijono0bc99a92011-03-17 04:34:43 +0000771 if (!call_med)
Benny Prijonof76e1392008-06-06 14:51:48 +0000772 return;
773
774 switch (op) {
775 case PJ_ICE_STRANS_OP_INIT:
Sauw Ming73ecfe82011-09-21 10:20:01 +0000776 call_med->tp_ready = result;
777 if (call_med->med_create_cb)
778 (*call_med->med_create_cb)(call_med, result,
779 call_med->call->secure_level, NULL);
Benny Prijonof76e1392008-06-06 14:51:48 +0000780 break;
781 case PJ_ICE_STRANS_OP_NEGOTIATION:
782 if (result != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000783 call_med->state = PJSUA_CALL_MEDIA_ERROR;
784 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijonof76e1392008-06-06 14:51:48 +0000785
Benny Prijono0bc99a92011-03-17 04:34:43 +0000786 if (call_med->call && pjsua_var.ua_cfg.cb.on_call_media_state) {
787 pjsua_var.ua_cfg.cb.on_call_media_state(call_med->call->index);
Benny Prijonof76e1392008-06-06 14:51:48 +0000788 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000789 } else if (call_med->call) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000790 /* Send UPDATE if default transport address is different than
791 * what was advertised (ticket #881)
792 */
793 pjmedia_transport_info tpinfo;
794 pjmedia_ice_transport_info *ii = NULL;
795 unsigned i;
796
797 pjmedia_transport_info_init(&tpinfo);
798 pjmedia_transport_get_info(tp, &tpinfo);
799 for (i=0; i<tpinfo.specific_info_cnt; ++i) {
800 if (tpinfo.spc_info[i].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
801 ii = (pjmedia_ice_transport_info*)
802 tpinfo.spc_info[i].buffer;
803 break;
804 }
805 }
806
807 if (ii && ii->role==PJ_ICE_SESS_ROLE_CONTROLLING &&
808 pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000809 &call_med->rtp_addr))
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000810 {
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000811 pj_bool_t use_update;
812 const pj_str_t STR_UPDATE = { "UPDATE", 6 };
813 pjsip_dialog_cap_status support_update;
814 pjsip_dialog *dlg;
815
Benny Prijono0bc99a92011-03-17 04:34:43 +0000816 dlg = call_med->call->inv->dlg;
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000817 support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW,
818 NULL, &STR_UPDATE);
819 use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED);
820
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000821 PJ_LOG(4,(THIS_FILE,
822 "ICE default transport address has changed for "
Benny Prijono0bc99a92011-03-17 04:34:43 +0000823 "call %d, sending %s", call_med->call->index,
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000824 (use_update ? "UPDATE" : "re-INVITE")));
825
826 if (use_update)
Benny Prijono0bc99a92011-03-17 04:34:43 +0000827 pjsua_call_update(call_med->call->index, 0, NULL);
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000828 else
Benny Prijono0bc99a92011-03-17 04:34:43 +0000829 pjsua_call_reinvite(call_med->call->index, 0, NULL);
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000830 }
Benny Prijonof76e1392008-06-06 14:51:48 +0000831 }
832 break;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000833 case PJ_ICE_STRANS_OP_KEEP_ALIVE:
834 if (result != PJ_SUCCESS) {
835 PJ_PERROR(4,(THIS_FILE, result,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000836 "ICE keep alive failure for transport %d:%d",
837 call_med->call->index, call_med->idx));
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000838 }
Sauw Ming73ecfe82011-09-21 10:20:01 +0000839 if (pjsua_var.ua_cfg.cb.on_call_media_transport_state) {
840 pjsua_med_tp_state_info info;
841
842 pj_bzero(&info, sizeof(info));
843 info.med_idx = call_med->idx;
844 info.state = call_med->tp_st;
845 info.status = result;
846 info.ext_info = &op;
847 (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)(
848 call_med->call->index, &info);
849 }
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000850 if (pjsua_var.ua_cfg.cb.on_ice_transport_error) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000851 pjsua_call_id id = call_med->call->index;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000852 (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result,
853 NULL);
854 }
855 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000856 }
857}
858
859
Benny Prijonof76e1392008-06-06 14:51:48 +0000860/* Parse "HOST:PORT" format */
861static pj_status_t parse_host_port(const pj_str_t *host_port,
862 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000863{
Benny Prijonof76e1392008-06-06 14:51:48 +0000864 pj_str_t str_port;
865
866 str_port.ptr = pj_strchr(host_port, ':');
867 if (str_port.ptr != NULL) {
868 int iport;
869
870 host->ptr = host_port->ptr;
871 host->slen = (str_port.ptr - host->ptr);
872 str_port.ptr++;
873 str_port.slen = host_port->slen - host->slen - 1;
874 iport = (int)pj_strtoul(&str_port);
875 if (iport < 1 || iport > 65535)
876 return PJ_EINVAL;
877 *port = (pj_uint16_t)iport;
878 } else {
879 *host = *host_port;
880 *port = 0;
881 }
882
883 return PJ_SUCCESS;
884}
885
886/* Create ICE media transports (when ice is enabled) */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000887static pj_status_t create_ice_media_transport(
888 const pjsua_transport_config *cfg,
Sauw Ming73ecfe82011-09-21 10:20:01 +0000889 pjsua_call_media *call_med,
890 pj_bool_t async)
Benny Prijonof76e1392008-06-06 14:51:48 +0000891{
892 char stunip[PJ_INET6_ADDRSTRLEN];
893 pj_ice_strans_cfg ice_cfg;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000894 pjmedia_ice_cb ice_cb;
895 char name[32];
896 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000897 pj_status_t status;
898
Benny Prijonoda9785b2007-04-02 20:43:06 +0000899 /* Make sure STUN server resolution has completed */
Benny Prijonobb995fd2009-08-12 11:03:23 +0000900 status = resolve_stun_server(PJ_TRUE);
Benny Prijonoda9785b2007-04-02 20:43:06 +0000901 if (status != PJ_SUCCESS) {
902 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
903 return status;
904 }
905
Benny Prijonof76e1392008-06-06 14:51:48 +0000906 /* Create ICE stream transport configuration */
907 pj_ice_strans_cfg_default(&ice_cfg);
908 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
909 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
910 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
911
912 ice_cfg.af = pj_AF_INET();
913 ice_cfg.resolver = pjsua_var.resolver;
914
Benny Prijono329d6382009-05-29 13:04:03 +0000915 ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
916
Benny Prijonof76e1392008-06-06 14:51:48 +0000917 /* Configure STUN settings */
918 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
919 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
920 ice_cfg.stun.server = pj_str(stunip);
921 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
922 }
Benny Prijono329d6382009-05-29 13:04:03 +0000923 if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
924 ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
Benny Prijonof76e1392008-06-06 14:51:48 +0000925
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000926 /* Copy QoS setting to STUN setting */
927 ice_cfg.stun.cfg.qos_type = cfg->qos_type;
928 pj_memcpy(&ice_cfg.stun.cfg.qos_params, &cfg->qos_params,
929 sizeof(cfg->qos_params));
930
Benny Prijonof76e1392008-06-06 14:51:48 +0000931 /* Configure TURN settings */
932 if (pjsua_var.media_cfg.enable_turn) {
933 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
934 &ice_cfg.turn.server,
935 &ice_cfg.turn.port);
936 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
937 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
938 return PJ_EINVAL;
939 }
940 if (ice_cfg.turn.port == 0)
941 ice_cfg.turn.port = 3479;
942 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
943 pj_memcpy(&ice_cfg.turn.auth_cred,
944 &pjsua_var.media_cfg.turn_auth_cred,
945 sizeof(ice_cfg.turn.auth_cred));
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000946
947 /* Copy QoS setting to TURN setting */
948 ice_cfg.turn.cfg.qos_type = cfg->qos_type;
949 pj_memcpy(&ice_cfg.turn.cfg.qos_params, &cfg->qos_params,
950 sizeof(cfg->qos_params));
Benny Prijonof76e1392008-06-06 14:51:48 +0000951 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000952
Benny Prijono0bc99a92011-03-17 04:34:43 +0000953 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
954 ice_cb.on_ice_complete = &on_ice_complete;
955 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", call_med->idx);
956 call_med->tp_ready = PJ_EPENDING;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000957
Benny Prijono0bc99a92011-03-17 04:34:43 +0000958 comp_cnt = 1;
959 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
960 ++comp_cnt;
Benny Prijonof76e1392008-06-06 14:51:48 +0000961
Benny Prijonobd6613f2011-04-11 17:27:14 +0000962 status = pjmedia_ice_create3(pjsua_var.med_endpt, name, comp_cnt,
963 &ice_cfg, &ice_cb, 0, call_med,
964 &call_med->tp);
Benny Prijono0bc99a92011-03-17 04:34:43 +0000965 if (status != PJ_SUCCESS) {
966 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
967 status);
968 goto on_error;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000969 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000970
Benny Prijono0bc99a92011-03-17 04:34:43 +0000971 /* Wait until transport is initialized, or time out */
Sauw Ming73ecfe82011-09-21 10:20:01 +0000972 if (!async) {
973 PJSUA_UNLOCK();
974 while (call_med->tp_ready == PJ_EPENDING) {
975 pjsua_handle_events(100);
976 }
977 PJSUA_LOCK();
Benny Prijono0bc99a92011-03-17 04:34:43 +0000978 }
Sauw Ming73ecfe82011-09-21 10:20:01 +0000979
980 if (async && call_med->tp_ready == PJ_EPENDING) {
981 return PJ_EPENDING;
982 } else if (call_med->tp_ready != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000983 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
984 call_med->tp_ready);
985 status = call_med->tp_ready;
986 goto on_error;
987 }
988
989 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
990 pjsua_var.media_cfg.tx_drop_pct);
991
992 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
993 pjsua_var.media_cfg.rx_drop_pct);
994
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000995 return PJ_SUCCESS;
996
997on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000998 if (call_med->tp != NULL) {
999 pjmedia_transport_close(call_med->tp);
1000 call_med->tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001001 }
1002
Benny Prijonoc97608e2007-03-23 16:34:20 +00001003 return status;
1004}
1005
Benny Prijono0bc99a92011-03-17 04:34:43 +00001006#if DISABLED_FOR_TICKET_1185
1007/* Create ICE media transports (when ice is enabled) */
1008static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
1009{
1010 unsigned i;
1011 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001012
Benny Prijono0bc99a92011-03-17 04:34:43 +00001013 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
1014 pjsua_call *call = &pjsua_var.calls[i];
1015 unsigned strm_idx;
1016
1017 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1018 pjsua_call_media *call_med = &call->media[strm_idx];
1019
1020 status = create_ice_media_transport(cfg, call_med);
1021 if (status != PJ_SUCCESS)
1022 goto on_error;
1023 }
1024 }
1025
1026 return PJ_SUCCESS;
1027
1028on_error:
1029 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
1030 pjsua_call *call = &pjsua_var.calls[i];
1031 unsigned strm_idx;
1032
1033 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1034 pjsua_call_media *call_med = &call->media[strm_idx];
1035
1036 if (call_med->tp) {
1037 pjmedia_transport_close(call_med->tp);
1038 call_med->tp = NULL;
1039 }
1040 }
1041 }
1042 return status;
1043}
1044#endif
1045
1046#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +00001047/*
Benny Prijono0bc99a92011-03-17 04:34:43 +00001048 * Create media transports for all the calls. This function creates
Benny Prijonoc97608e2007-03-23 16:34:20 +00001049 * one UDP media transport for each call.
1050 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001051PJ_DEF(pj_status_t) pjsua_media_transports_create(
1052 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001053{
1054 pjsua_transport_config cfg;
1055 unsigned i;
1056 pj_status_t status;
1057
1058
1059 /* Make sure pjsua_init() has been called */
1060 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
1061
1062 PJSUA_LOCK();
1063
1064 /* Delete existing media transports */
1065 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001066 pjsua_call *call = &pjsua_var.calls[i];
1067 unsigned strm_idx;
1068
1069 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1070 pjsua_call_media *call_med = &call->media[strm_idx];
1071
1072 if (call_med->tp && call_med->tp_auto_del) {
1073 pjmedia_transport_close(call_med->tp);
1074 call_med->tp = NULL;
1075 call_med->tp_orig = NULL;
1076 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001077 }
1078 }
1079
1080 /* Copy config */
1081 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
1082
Benny Prijono40860c32008-09-04 13:55:33 +00001083 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001084 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijono4d79b0f2009-10-25 09:02:07 +00001085 status = create_ice_media_transports(&cfg);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001086 } else {
1087 status = create_udp_media_transports(&cfg);
1088 }
1089
Benny Prijono40860c32008-09-04 13:55:33 +00001090 /* Set media transport auto_delete to True */
1091 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001092 pjsua_call *call = &pjsua_var.calls[i];
1093 unsigned strm_idx;
1094
1095 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1096 pjsua_call_media *call_med = &call->media[strm_idx];
1097
1098 call_med->tp_auto_del = PJ_TRUE;
1099 }
Benny Prijono40860c32008-09-04 13:55:33 +00001100 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001101
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001102 PJSUA_UNLOCK();
1103
1104 return status;
1105}
1106
Benny Prijono40860c32008-09-04 13:55:33 +00001107/*
1108 * Attach application's created media transports.
1109 */
1110PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
1111 unsigned count,
1112 pj_bool_t auto_delete)
1113{
1114 unsigned i;
1115
1116 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
1117
1118 /* Assign the media transports */
1119 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001120 pjsua_call *call = &pjsua_var.calls[i];
1121 unsigned strm_idx;
1122
1123 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1124 pjsua_call_media *call_med = &call->media[strm_idx];
1125
1126 if (call_med->tp && call_med->tp_auto_del) {
1127 pjmedia_transport_close(call_med->tp);
1128 call_med->tp = NULL;
1129 call_med->tp_orig = NULL;
1130 }
Benny Prijono40860c32008-09-04 13:55:33 +00001131 }
1132
Benny Prijono0bc99a92011-03-17 04:34:43 +00001133 PJ_TODO(remove_pjsua_media_transports_attach);
1134
1135 call->media[0].tp = tp[i].transport;
1136 call->media[0].tp_auto_del = auto_delete;
Benny Prijono40860c32008-09-04 13:55:33 +00001137 }
1138
1139 return PJ_SUCCESS;
1140}
Benny Prijono0bc99a92011-03-17 04:34:43 +00001141#endif
Benny Prijono40860c32008-09-04 13:55:33 +00001142
Benny Prijono0bc99a92011-03-17 04:34:43 +00001143/* Go through the list of media in the SDP, find acceptable media, and
1144 * sort them based on the "quality" of the media, and store the indexes
1145 * in the specified array. Media with the best quality will be listed
1146 * first in the array. The quality factors considered currently is
1147 * encryption.
1148 */
1149static void sort_media(const pjmedia_sdp_session *sdp,
1150 const pj_str_t *type,
1151 pjmedia_srtp_use use_srtp,
1152 pj_uint8_t midx[],
1153 unsigned *p_count)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001154{
1155 unsigned i;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001156 unsigned count = 0;
1157 int score[PJSUA_MAX_CALL_MEDIA];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001158
Benny Prijono0bc99a92011-03-17 04:34:43 +00001159 pj_assert(*p_count >= PJSUA_MAX_CALL_MEDIA);
1160
1161 *p_count = 0;
Benny Prijono09c0d672011-04-11 05:03:24 +00001162 for (i=0; i<PJSUA_MAX_CALL_MEDIA; ++i)
1163 score[i] = 1;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001164
1165 /* Score each media */
1166 for (i=0; i<sdp->media_count && count<PJSUA_MAX_CALL_MEDIA; ++i) {
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001167 const pjmedia_sdp_media *m = sdp->media[i];
Nanang Izzuddina6414292011-04-08 04:26:18 +00001168 const pjmedia_sdp_conn *c;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001169
Benny Prijono0bc99a92011-03-17 04:34:43 +00001170 /* Skip different media */
1171 if (pj_stricmp(&m->desc.media, type) != 0) {
1172 score[count++] = -22000;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001173 continue;
1174 }
1175
Nanang Izzuddina6414292011-04-08 04:26:18 +00001176 c = m->conn? m->conn : sdp->conn;
1177
Benny Prijono0bc99a92011-03-17 04:34:43 +00001178 /* Supported transports */
1179 if (pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0) {
1180 switch (use_srtp) {
1181 case PJMEDIA_SRTP_MANDATORY:
1182 case PJMEDIA_SRTP_OPTIONAL:
1183 ++score[i];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001184 break;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001185 case PJMEDIA_SRTP_DISABLED:
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001186 //--score[i];
1187 score[i] -= 5;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001188 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001189 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001190 } else if (pj_stricmp2(&m->desc.transport, "RTP/AVP")==0) {
1191 switch (use_srtp) {
1192 case PJMEDIA_SRTP_MANDATORY:
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001193 //--score[i];
1194 score[i] -= 5;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001195 break;
1196 case PJMEDIA_SRTP_OPTIONAL:
1197 /* No change in score */
1198 break;
1199 case PJMEDIA_SRTP_DISABLED:
1200 ++score[i];
1201 break;
1202 }
1203 } else {
1204 score[i] -= 10;
1205 }
1206
1207 /* Is media disabled? */
1208 if (m->desc.port == 0)
1209 score[i] -= 10;
1210
1211 /* Is media inactive? */
Nanang Izzuddina6414292011-04-08 04:26:18 +00001212 if (pjmedia_sdp_media_find_attr2(m, "inactive", NULL) ||
1213 pj_strcmp2(&c->addr, "0.0.0.0") == 0)
1214 {
1215 //score[i] -= 10;
1216 score[i] -= 1;
1217 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001218
1219 ++count;
1220 }
1221
1222 /* Created sorted list based on quality */
1223 for (i=0; i<count; ++i) {
1224 unsigned j;
1225 int best = 0;
1226
1227 for (j=1; j<count; ++j) {
1228 if (score[j] > score[best])
1229 best = j;
1230 }
1231 /* Don't put media with negative score, that media is unacceptable
1232 * for us.
1233 */
1234 if (score[best] >= 0) {
1235 midx[*p_count] = (pj_uint8_t)best;
1236 (*p_count)++;
1237 }
1238
1239 score[best] = -22000;
1240
1241 }
1242}
1243
Benny Prijonoee0ba182011-07-15 06:18:29 +00001244/* Callback to receive media events */
1245static pj_status_t call_media_on_event(pjmedia_event_subscription *esub,
1246 pjmedia_event *event)
1247{
1248 pjsua_call_media *call_med = (pjsua_call_media*)esub->user_data;
1249 pjsua_call *call = call_med->call;
1250
1251 if (pjsua_var.ua_cfg.cb.on_call_media_event && call) {
1252 ++event->proc_cnt;
1253 (*pjsua_var.ua_cfg.cb.on_call_media_event)(call->index,
1254 call_med->idx, event);
Benny Prijono53a7c702008-04-14 02:57:29 +00001255 }
1256
1257 return PJ_SUCCESS;
1258}
1259
Sauw Ming73ecfe82011-09-21 10:20:01 +00001260/* Set media transport state and notify the application via the callback. */
1261void set_media_tp_state(pjsua_call_media *call_med,
1262 pjsua_med_tp_st tp_st)
1263{
1264 if (pjsua_var.ua_cfg.cb.on_call_media_transport_state &&
1265 call_med->tp_st != tp_st)
1266 {
1267 pjsua_med_tp_state_info info;
1268
1269 pj_bzero(&info, sizeof(info));
1270 info.med_idx = call_med->idx;
1271 info.state = tp_st;
1272 info.status = call_med->tp_ready;
1273 (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)(
1274 call_med->call->index, &info);
1275 }
1276
1277 call_med->tp_st = tp_st;
1278}
1279
1280/* Callback to resume pjsua_call_media_init() after media transport
1281 * creation is completed.
1282 */
1283static pj_status_t call_media_init_cb(pjsua_call_media *call_med,
1284 pj_status_t status,
1285 int security_level,
1286 int *sip_err_code)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001287{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001288 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
Sauw Ming73ecfe82011-09-21 10:20:01 +00001289 int err_code = 0;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001290
Sauw Ming73ecfe82011-09-21 10:20:01 +00001291 if (status != PJ_SUCCESS)
1292 goto on_error;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001293
Sauw Ming73ecfe82011-09-21 10:20:01 +00001294 if (call_med->tp_st == PJSUA_MED_TP_CREATING)
1295 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001296
Benny Prijono0bc99a92011-03-17 04:34:43 +00001297#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1298 /* This function may be called when SRTP transport already exists
1299 * (e.g: in re-invite, update), don't need to destroy/re-create.
1300 */
1301 if (!call_med->tp_orig || call_med->tp == call_med->tp_orig) {
1302 pjmedia_srtp_setting srtp_opt;
1303 pjmedia_transport *srtp = NULL;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001304
Benny Prijono0bc99a92011-03-17 04:34:43 +00001305 /* Check if SRTP requires secure signaling */
1306 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
1307 if (security_level < acc->cfg.srtp_secure_signaling) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001308 err_code = PJSIP_SC_NOT_ACCEPTABLE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001309 status = PJSIP_ESESSIONINSECURE;
1310 goto on_error;
1311 }
1312 }
1313
1314 /* Always create SRTP adapter */
1315 pjmedia_srtp_setting_default(&srtp_opt);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001316 srtp_opt.close_member_tp = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001317 /* If media session has been ever established, let's use remote's
1318 * preference in SRTP usage policy, especially when it is stricter.
1319 */
1320 if (call_med->rem_srtp_use > acc->cfg.use_srtp)
1321 srtp_opt.use = call_med->rem_srtp_use;
1322 else
1323 srtp_opt.use = acc->cfg.use_srtp;
1324
1325 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
1326 call_med->tp,
1327 &srtp_opt, &srtp);
1328 if (status != PJ_SUCCESS) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001329 err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001330 goto on_error;
1331 }
1332
1333 /* Set SRTP as current media transport */
1334 call_med->tp_orig = call_med->tp;
1335 call_med->tp = srtp;
1336 }
1337#else
Benny Prijono7df19342011-07-23 02:54:03 +00001338 call_med->tp_orig = call_med->tp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001339 PJ_UNUSED_ARG(security_level);
1340#endif
1341
Benny Prijono0bc99a92011-03-17 04:34:43 +00001342on_error:
Sauw Ming73ecfe82011-09-21 10:20:01 +00001343 if (status != PJ_SUCCESS && call_med->tp) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001344 pjmedia_transport_close(call_med->tp);
1345 call_med->tp = NULL;
1346 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001347
1348 if (sip_err_code)
1349 *sip_err_code = err_code;
1350
1351 if (call_med->med_init_cb) {
1352 pjsua_med_tp_state_info info;
1353
1354 pj_bzero(&info, sizeof(info));
1355 info.status = status;
1356 info.state = call_med->tp_st;
1357 info.med_idx = call_med->idx;
1358 info.sip_err_code = err_code;
1359 (*call_med->med_init_cb)(call_med->call->index, &info);
1360 }
1361
1362 return status;
1363}
1364
1365/* Initialize the media line */
1366pj_status_t pjsua_call_media_init(pjsua_call_media *call_med,
1367 pjmedia_type type,
1368 const pjsua_transport_config *tcfg,
1369 int security_level,
1370 int *sip_err_code,
1371 pj_bool_t async,
1372 pjsua_med_tp_state_cb cb)
1373{
1374 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
1375 pj_status_t status = PJ_SUCCESS;
1376
1377 /*
1378 * Note: this function may be called when the media already exists
1379 * (e.g. in reinvites, updates, etc.)
1380 */
1381 call_med->type = type;
1382
1383 /* Create the media transport for initial call. */
1384 if (call_med->tp == NULL) {
1385#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
1386 /* While in initial call, set default video devices */
1387 if (type == PJMEDIA_TYPE_VIDEO) {
1388 call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev;
1389 call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev;
1390 if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) {
1391 pjmedia_vid_dev_info info;
1392 pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info);
1393 call_med->strm.v.rdr_dev = info.id;
1394 }
1395 if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
1396 pjmedia_vid_dev_info info;
1397 pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info);
1398 call_med->strm.v.cap_dev = info.id;
1399 }
Nanang Izzuddinc12a19d2011-10-03 05:23:59 +00001400
1401 /* Init event subscribtion */
1402 pjmedia_event_subscription_init(&call_med->esub_rend, &call_media_on_event,
1403 call_med);
1404 pjmedia_event_subscription_init(&call_med->esub_cap, &call_media_on_event,
1405 call_med);
Sauw Ming73ecfe82011-09-21 10:20:01 +00001406 }
1407#endif
1408
1409 set_media_tp_state(call_med, PJSUA_MED_TP_CREATING);
1410
1411 if (async) {
1412 call_med->med_create_cb = &call_media_init_cb;
1413 call_med->med_init_cb = cb;
1414 }
1415
1416 if (pjsua_var.media_cfg.enable_ice) {
1417 status = create_ice_media_transport(tcfg, call_med, async);
Sauw Ming848742f2011-09-28 04:20:30 +00001418 if (async && status == PJ_SUCCESS) {
1419 /* Callback has been called. */
1420 call_med->med_init_cb = NULL;
1421 /* We cannot return PJ_SUCCESS here since we already call
1422 * the callback.
1423 */
1424 return PJ_EPENDING;
1425 } else if (async && status == PJ_EPENDING) {
1426 /* We will resume call media initialization in the
1427 * on_ice_complete() callback.
1428 */
1429 return PJ_EPENDING;
1430 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001431 } else {
1432 status = create_udp_media_transport(tcfg, call_med);
1433 }
1434
Sauw Ming848742f2011-09-28 04:20:30 +00001435 if (status != PJ_SUCCESS) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001436 PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport"));
1437 return status;
1438 }
1439
1440 /* Media transport creation completed immediately, so
1441 * we don't need to call the callback.
1442 */
1443 call_med->med_init_cb = NULL;
1444
1445 } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) {
1446 /* Media is being reenabled. */
1447 set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
1448 }
1449
1450 return call_media_init_cb(call_med, status, security_level,
1451 sip_err_code);
1452}
1453
1454/* Callback to resume pjsua_media_channel_init() after media transport
1455 * initialization is completed.
1456 */
1457static pj_status_t media_channel_init_cb(pjsua_call_id call_id,
1458 const pjsua_med_tp_state_info *info)
1459{
1460 pjsua_call *call = &pjsua_var.calls[call_id];
1461 pj_status_t status = (info? info->status : PJ_SUCCESS);
1462 unsigned mi;
1463
1464 if (info) {
1465 pj_mutex_lock(call->med_ch_mutex);
1466
1467 /* Set the callback to NULL to indicate that the async operation
1468 * has completed.
1469 */
1470 call->media[info->med_idx].med_init_cb = NULL;
1471
1472 /* In case of failure, save the information to be returned
1473 * by the last media transport to finish.
1474 */
1475 if (info->status != PJ_SUCCESS)
1476 pj_memcpy(&call->med_ch_info, info, sizeof(info));
1477
1478 /* Check whether all the call's medias have finished calling their
1479 * callbacks.
1480 */
1481 for (mi=0; mi < call->med_cnt; ++mi) {
1482 pjsua_call_media *call_med = &call->media[mi];
1483
Sauw Ming3a55bb92011-10-06 06:49:09 +00001484 if (call_med->med_init_cb ||
1485 call_med->tp_st == PJSUA_MED_TP_NULL)
1486 {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001487 pj_mutex_unlock(call->med_ch_mutex);
1488 return PJ_SUCCESS;
1489 }
1490
1491 if (call_med->tp_ready != PJ_SUCCESS)
1492 status = call_med->tp_ready;
1493 }
1494
1495 /* OK, we are called by the last media transport finished. */
1496 pj_mutex_unlock(call->med_ch_mutex);
1497 }
1498
1499 if (call->med_ch_mutex) {
1500 pj_mutex_destroy(call->med_ch_mutex);
1501 call->med_ch_mutex = NULL;
1502 }
1503
1504 if (status != PJ_SUCCESS) {
1505 pjsua_media_channel_deinit(call_id);
1506 goto on_error;
1507 }
1508
1509 /* Tell the media transport of a new offer/answer session */
1510 for (mi=0; mi < call->med_cnt; ++mi) {
1511 pjsua_call_media *call_med = &call->media[mi];
1512
1513 /* Note: tp may be NULL if this media line is disabled */
1514 if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) {
Sauw Ming99cc8ff2011-09-22 04:24:56 +00001515 pj_pool_t *tmp_pool = call->async_call.pool_prov;
1516
1517 if (!tmp_pool) {
1518 tmp_pool = (call->inv? call->inv->pool_prov:
1519 call->async_call.dlg->pool);
1520 }
Sauw Ming73ecfe82011-09-21 10:20:01 +00001521
1522 status = pjmedia_transport_media_create(
1523 call_med->tp, tmp_pool,
1524 0, call->async_call.rem_sdp, mi);
1525 if (status != PJ_SUCCESS) {
1526 call->med_ch_info.status = status;
1527 call->med_ch_info.med_idx = mi;
1528 call->med_ch_info.state = call_med->tp_st;
1529 call->med_ch_info.sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1530 pjsua_media_channel_deinit(call_id);
1531 goto on_error;
1532 }
1533
1534 set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
1535 }
1536 }
1537
1538 call->med_ch_info.status = PJ_SUCCESS;
1539
1540on_error:
1541 if (call->med_ch_cb)
1542 (*call->med_ch_cb)(call->index, &call->med_ch_info);
1543
Benny Prijono0bc99a92011-03-17 04:34:43 +00001544 return status;
1545}
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001546
Benny Prijonod8179652008-01-23 20:39:07 +00001547pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
1548 pjsip_role_e role,
1549 int security_level,
1550 pj_pool_t *tmp_pool,
1551 const pjmedia_sdp_session *rem_sdp,
Sauw Ming73ecfe82011-09-21 10:20:01 +00001552 int *sip_err_code,
1553 pj_bool_t async,
1554 pjsua_med_tp_state_cb cb)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001555{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001556 const pj_str_t STR_AUDIO = { "audio", 5 };
1557 const pj_str_t STR_VIDEO = { "video", 5 };
Benny Prijonod8179652008-01-23 20:39:07 +00001558 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijonod8179652008-01-23 20:39:07 +00001559 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001560 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1561 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
1562 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
1563 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
1564 pjmedia_type media_types[PJSUA_MAX_CALL_MEDIA];
1565 unsigned mi;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001566 pj_bool_t pending_med_tp = PJ_FALSE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001567 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001568
Benny Prijonod8179652008-01-23 20:39:07 +00001569 PJ_UNUSED_ARG(role);
1570
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001571 /*
1572 * Note: this function may be called when the media already exists
1573 * (e.g. in reinvites, updates, etc).
1574 */
1575
Benny Prijono0bc99a92011-03-17 04:34:43 +00001576 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
1577 return PJ_EBUSY;
1578
Sauw Ming73ecfe82011-09-21 10:20:01 +00001579 if (async) {
1580 pj_pool_t *tmppool = (call->inv? call->inv->pool_prov:
1581 call->async_call.dlg->pool);
1582
1583 status = pj_mutex_create_simple(tmppool, NULL, &call->med_ch_mutex);
1584 if (status != PJ_SUCCESS)
1585 return status;
1586 }
1587
Benny Prijonob90fd382011-09-18 14:59:56 +00001588 PJ_LOG(4,(THIS_FILE, "Call %d: initializing media..", call_id));
1589 pj_log_push_indent();
1590
Benny Prijono0bc99a92011-03-17 04:34:43 +00001591#if DISABLED_FOR_TICKET_1185
Benny Prijonod8179652008-01-23 20:39:07 +00001592 /* Return error if media transport has not been created yet
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001593 * (e.g. application is starting)
1594 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001595 for (i=0; i<call->med_cnt; ++i) {
1596 if (call->media[i].tp == NULL) {
Benny Prijonob90fd382011-09-18 14:59:56 +00001597 status = PJ_EBUSY;
1598 goto on_error;
Benny Prijonod8179652008-01-23 20:39:07 +00001599 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001600 }
Benny Prijonod8179652008-01-23 20:39:07 +00001601#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +00001602
Benny Prijono0bc99a92011-03-17 04:34:43 +00001603 if (rem_sdp) {
1604 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
1605 maudidx, &maudcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001606 // Don't apply media count limitation until SDP negotiation is done.
1607 //if (maudcnt > acc->cfg.max_audio_cnt)
1608 // maudcnt = acc->cfg.max_audio_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001609
Benny Prijono0bc99a92011-03-17 04:34:43 +00001610 if (maudcnt==0) {
1611 /* Expecting audio in the offer */
1612 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1613 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001614 status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
1615 goto on_error;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001616 }
1617
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001618#if PJMEDIA_HAS_VIDEO
Benny Prijono0bc99a92011-03-17 04:34:43 +00001619 sort_media(rem_sdp, &STR_VIDEO, acc->cfg.use_srtp,
1620 mvididx, &mvidcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001621 // Don't apply media count limitation until SDP negotiation is done.
1622 //if (mvidcnt > acc->cfg.max_video_cnt)
1623 //mvidcnt = acc->cfg.max_video_cnt;
1624#else
1625 mvidcnt = 0;
1626#endif
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001627
1628 /* Update media count only when remote add any media, this media count
1629 * must never decrease.
Benny Prijonod8179652008-01-23 20:39:07 +00001630 */
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001631 if (call->med_cnt < rem_sdp->media_count)
1632 call->med_cnt = PJ_MIN(rem_sdp->media_count, PJSUA_MAX_CALL_MEDIA);
Benny Prijonod8179652008-01-23 20:39:07 +00001633
Benny Prijono0bc99a92011-03-17 04:34:43 +00001634 } else {
1635 maudcnt = acc->cfg.max_audio_cnt;
1636 for (mi=0; mi<maudcnt; ++mi) {
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001637 maudidx[mi] = (pj_uint8_t)mi;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001638 media_types[mi] = PJMEDIA_TYPE_AUDIO;
Benny Prijonod8179652008-01-23 20:39:07 +00001639 }
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001640#if PJMEDIA_HAS_VIDEO
Benny Prijono0bc99a92011-03-17 04:34:43 +00001641 mvidcnt = acc->cfg.max_video_cnt;
1642 for (mi=0; mi<mvidcnt; ++mi) {
1643 media_types[maudcnt + mi] = PJMEDIA_TYPE_VIDEO;
1644 }
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001645#else
1646 mvidcnt = 0;
1647#endif
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001648 call->med_cnt = maudcnt + mvidcnt;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001649 }
1650
Benny Prijono0bc99a92011-03-17 04:34:43 +00001651 if (call->med_cnt == 0) {
1652 /* Expecting at least one media */
Benny Prijonoab8dba92008-06-27 21:59:15 +00001653 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001654 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001655 status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
1656 goto on_error;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001657 }
1658
Sauw Ming73ecfe82011-09-21 10:20:01 +00001659 if (async) {
1660 call->med_ch_cb = cb;
1661 if (rem_sdp) {
Sauw Ming73ecfe82011-09-21 10:20:01 +00001662 call->async_call.rem_sdp =
1663 pjmedia_sdp_session_clone(call->inv->pool_prov, rem_sdp);
1664 }
1665 }
1666
Sauw Ming99cc8ff2011-09-22 04:24:56 +00001667 call->async_call.pool_prov = tmp_pool;
1668
Benny Prijono0bc99a92011-03-17 04:34:43 +00001669 /* Initialize each media line */
1670 for (mi=0; mi < call->med_cnt; ++mi) {
1671 pjsua_call_media *call_med = &call->media[mi];
1672 pj_bool_t enabled = PJ_FALSE;
1673 pjmedia_type media_type = PJMEDIA_TYPE_NONE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001674
Benny Prijono0bc99a92011-03-17 04:34:43 +00001675 if (rem_sdp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001676 if (mi >= rem_sdp->media_count) {
1677 /* Media has been removed in remote re-offer */
1678 media_type = call_med->type;
1679 } else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_AUDIO)) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001680 media_type = PJMEDIA_TYPE_AUDIO;
1681 if (pj_memchr(maudidx, mi, maudcnt * sizeof(maudidx[0]))) {
1682 enabled = PJ_TRUE;
1683 }
1684 }
1685 else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_VIDEO)) {
1686 media_type = PJMEDIA_TYPE_VIDEO;
1687 if (pj_memchr(mvididx, mi, mvidcnt * sizeof(mvididx[0]))) {
1688 enabled = PJ_TRUE;
1689 }
1690 }
1691
1692 } else {
1693 enabled = PJ_TRUE;
1694 media_type = media_types[mi];
1695 }
1696
1697 if (enabled) {
1698 status = pjsua_call_media_init(call_med, media_type,
1699 &acc->cfg.rtp_cfg,
Sauw Ming73ecfe82011-09-21 10:20:01 +00001700 security_level, sip_err_code,
1701 async,
Sauw Ming3a55bb92011-10-06 06:49:09 +00001702 (async? &media_channel_init_cb:
1703 NULL));
Sauw Ming73ecfe82011-09-21 10:20:01 +00001704 if (status == PJ_EPENDING) {
1705 pending_med_tp = PJ_TRUE;
1706 } else if (status != PJ_SUCCESS) {
1707 if (pending_med_tp) {
1708 /* Save failure information. */
1709 call_med->tp_ready = status;
1710 pj_bzero(&call->med_ch_info, sizeof(call->med_ch_info));
1711 call->med_ch_info.status = status;
1712 call->med_ch_info.state = call_med->tp_st;
1713 call->med_ch_info.med_idx = call_med->idx;
1714 if (sip_err_code)
1715 call->med_ch_info.sip_err_code = *sip_err_code;
1716
1717 /* We will return failure in the callback later. */
1718 return PJ_EPENDING;
1719 }
1720
1721 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001722 goto on_error;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001723 }
1724 } else {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001725 /* By convention, the media is disabled if transport is NULL
1726 * or transport state is PJSUA_MED_TP_DISABLED.
1727 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001728 if (call_med->tp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001729 // Don't close transport here, as SDP negotiation has not been
1730 // done and stream may be still active.
1731 //pjmedia_transport_close(call_med->tp);
1732 //call_med->tp = NULL;
1733 pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT ||
1734 call_med->tp_st == PJSUA_MED_TP_RUNNING);
Sauw Ming73ecfe82011-09-21 10:20:01 +00001735 set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001736 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001737
1738 /* Put media type just for info */
1739 call_med->type = media_type;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001740 }
Benny Prijono224b4e22008-06-19 14:10:28 +00001741 }
1742
Benny Prijono0bc99a92011-03-17 04:34:43 +00001743 call->audio_idx = maudidx[0];
1744
1745 PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d",
1746 call->audio_idx, call->index));
1747
Sauw Ming73ecfe82011-09-21 10:20:01 +00001748 if (pending_med_tp) {
Sauw Ming99cc8ff2011-09-22 04:24:56 +00001749 /* We shouldn't use temporary pool anymore. */
1750 call->async_call.pool_prov = NULL;
Sauw Ming73ecfe82011-09-21 10:20:01 +00001751 /* We have a pending media transport initialization. */
1752 pj_log_pop_indent();
1753 return PJ_EPENDING;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001754 }
1755
Sauw Ming73ecfe82011-09-21 10:20:01 +00001756 /* Media transport initialization completed immediately, so
1757 * we don't need to call the callback.
1758 */
1759 call->med_ch_cb = NULL;
1760
1761 status = media_channel_init_cb(call_id, NULL);
1762 if (status != PJ_SUCCESS && sip_err_code)
1763 *sip_err_code = call->med_ch_info.sip_err_code;
1764
Benny Prijonob90fd382011-09-18 14:59:56 +00001765 pj_log_pop_indent();
Sauw Ming73ecfe82011-09-21 10:20:01 +00001766 return status;
Benny Prijonob90fd382011-09-18 14:59:56 +00001767
1768on_error:
Sauw Ming73ecfe82011-09-21 10:20:01 +00001769 if (call->med_ch_mutex) {
1770 pj_mutex_destroy(call->med_ch_mutex);
1771 call->med_ch_mutex = NULL;
1772 }
1773
Benny Prijonob90fd382011-09-18 14:59:56 +00001774 pj_log_pop_indent();
1775 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001776}
1777
1778pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1779 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001780 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001781 pjmedia_sdp_session **p_sdp,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001782 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001783{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001784 enum { MAX_MEDIA = PJSUA_MAX_CALL_MEDIA };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001785 pjmedia_sdp_session *sdp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001786 pj_sockaddr origin;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001787 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001788 pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001789 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001790 pj_status_t status;
1791
Benny Prijono0bc99a92011-03-17 04:34:43 +00001792 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
Benny Prijono55e82352007-05-10 20:49:08 +00001793 return PJ_EBUSY;
Benny Prijono55e82352007-05-10 20:49:08 +00001794
Benny Prijono0bc99a92011-03-17 04:34:43 +00001795 if (rem_sdp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001796 /* If this is a re-offer, let's re-initialize media as remote may
1797 * add or remove media
1798 */
1799 if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) {
1800 status = pjsua_media_channel_init(call_id, PJSIP_ROLE_UAS,
1801 call->secure_level, pool,
Sauw Ming73ecfe82011-09-21 10:20:01 +00001802 rem_sdp, sip_err_code,
1803 PJ_FALSE, NULL);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001804 if (status != PJ_SUCCESS)
1805 return status;
Benny Prijono0324ba52010-12-02 10:41:46 +00001806 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001807
1808#if 0
1809 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001810 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1811 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001812
Benny Prijono0bc99a92011-03-17 04:34:43 +00001813 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
1814 maudidx, &maudcnt);
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001815
Benny Prijono0bc99a92011-03-17 04:34:43 +00001816 if (maudcnt==0) {
1817 /* Expecting audio in the offer */
1818 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1819 pjsua_media_channel_deinit(call_id);
1820 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijono224b4e22008-06-19 14:10:28 +00001821 }
Benny Prijono224b4e22008-06-19 14:10:28 +00001822
Benny Prijono0bc99a92011-03-17 04:34:43 +00001823 call->audio_idx = maudidx[0];
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001824#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00001825 } else {
1826 /* Audio is first in our offer, by convention */
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001827 // The audio_idx should not be changed here, as this function may be
1828 // called in generating re-offer and the current active audio index
1829 // can be anywhere.
1830 //call->audio_idx = 0;
Benny Prijono224b4e22008-06-19 14:10:28 +00001831 }
1832
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001833#if 0
1834 // Since r3512, old-style hold should have got transport, created by
1835 // pjsua_media_channel_init() in initial offer/answer or remote reoffer.
Benny Prijono224b4e22008-06-19 14:10:28 +00001836 /* Create media if it's not created. This could happen when call is
Benny Prijono0bc99a92011-03-17 04:34:43 +00001837 * currently on-hold (with the old style hold)
Benny Prijono224b4e22008-06-19 14:10:28 +00001838 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001839 if (call->media[call->audio_idx].tp == NULL) {
Benny Prijono224b4e22008-06-19 14:10:28 +00001840 pjsip_role_e role;
1841 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1842 status = pjsua_media_channel_init(call_id, role, call->secure_level,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001843 pool, rem_sdp, sip_err_code);
Benny Prijono224b4e22008-06-19 14:10:28 +00001844 if (status != PJ_SUCCESS)
1845 return status;
1846 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001847#endif
Benny Prijono224b4e22008-06-19 14:10:28 +00001848
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001849 /* Get SDP negotiator state */
1850 if (call->inv && call->inv->neg)
1851 sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg);
1852
Benny Prijono0bc99a92011-03-17 04:34:43 +00001853 /* Get one address to use in the origin field */
1854 pj_bzero(&origin, sizeof(origin));
1855 for (mi=0; mi<call->med_cnt; ++mi) {
1856 pjmedia_transport_info tpinfo;
Benny Prijono617c5bc2007-04-02 19:51:21 +00001857
Benny Prijono0bc99a92011-03-17 04:34:43 +00001858 if (call->media[mi].tp == NULL)
1859 continue;
1860
1861 pjmedia_transport_info_init(&tpinfo);
1862 pjmedia_transport_get_info(call->media[mi].tp, &tpinfo);
1863 pj_sockaddr_cp(&origin, &tpinfo.sock_info.rtp_addr_name);
1864 break;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001865 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001866
Benny Prijono0bc99a92011-03-17 04:34:43 +00001867 /* Create the base (blank) SDP */
1868 status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, pool, NULL,
1869 &origin, &sdp);
1870 if (status != PJ_SUCCESS)
1871 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001872
Benny Prijono0bc99a92011-03-17 04:34:43 +00001873 /* Process each media line */
1874 for (mi=0; mi<call->med_cnt; ++mi) {
1875 pjsua_call_media *call_med = &call->media[mi];
1876 pjmedia_sdp_media *m = NULL;
1877 pjmedia_transport_info tpinfo;
1878
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001879 if (rem_sdp && mi >= rem_sdp->media_count) {
1880 /* Remote might have removed some media lines. */
1881 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001882 }
1883
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001884 if (call_med->tp == NULL || call_med->tp_st == PJSUA_MED_TP_DISABLED)
1885 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001886 /*
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001887 * This media is disabled. Just create a valid SDP with zero
Benny Prijono0bc99a92011-03-17 04:34:43 +00001888 * port.
1889 */
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001890 if (rem_sdp) {
1891 /* Just clone the remote media and deactivate it */
1892 m = pjmedia_sdp_media_clone_deactivate(pool,
1893 rem_sdp->media[mi]);
1894 } else {
1895 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1896 m->desc.transport = pj_str("RTP/AVP");
1897 m->desc.fmt_count = 1;
1898 m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
1899 m->conn->net_type = pj_str("IN");
1900 m->conn->addr_type = pj_str("IP4");
1901 m->conn->addr = pj_str("127.0.0.1");
Benny Prijonoa310bd22008-06-27 21:19:44 +00001902
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001903 switch (call_med->type) {
1904 case PJMEDIA_TYPE_AUDIO:
1905 m->desc.media = pj_str("audio");
1906 m->desc.fmt[0] = pj_str("0");
1907 break;
1908 case PJMEDIA_TYPE_VIDEO:
1909 m->desc.media = pj_str("video");
1910 m->desc.fmt[0] = pj_str("31");
1911 break;
1912 default:
1913 if (rem_sdp) {
1914 pj_strdup(pool, &m->desc.media,
1915 &rem_sdp->media[mi]->desc.media);
1916 pj_strdup(pool, &m->desc.fmt[0],
1917 &rem_sdp->media[mi]->desc.fmt[0]);
1918 } else {
1919 pj_assert(!"Invalid call_med media type");
1920 return PJ_EBUG;
1921 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001922 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001923 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001924
1925 sdp->media[sdp->media_count++] = m;
1926 continue;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001927 }
1928
Benny Prijono0bc99a92011-03-17 04:34:43 +00001929 /* Get transport address info */
1930 pjmedia_transport_info_init(&tpinfo);
1931 pjmedia_transport_get_info(call_med->tp, &tpinfo);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001932
Benny Prijono0bc99a92011-03-17 04:34:43 +00001933 /* Ask pjmedia endpoint to create SDP media line */
1934 switch (call_med->type) {
1935 case PJMEDIA_TYPE_AUDIO:
1936 status = pjmedia_endpt_create_audio_sdp(pjsua_var.med_endpt, pool,
1937 &tpinfo.sock_info, 0, &m);
1938 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00001939#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00001940 case PJMEDIA_TYPE_VIDEO:
1941 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
1942 &tpinfo.sock_info, 0, &m);
1943 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00001944#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00001945 default:
1946 pj_assert(!"Invalid call_med media type");
1947 return PJ_EBUG;
1948 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001949
Benny Prijono0bc99a92011-03-17 04:34:43 +00001950 if (status != PJ_SUCCESS)
1951 return status;
1952
1953 sdp->media[sdp->media_count++] = m;
1954
1955 /* Give to transport */
1956 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
1957 sdp, rem_sdp, mi);
1958 if (status != PJ_SUCCESS) {
1959 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1960 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001961 }
Nanang Izzuddin91ba2a22011-04-11 19:59:09 +00001962
1963 /* Copy c= line of the first media to session level,
1964 * if there's none.
1965 */
1966 if (sdp->conn == NULL) {
1967 sdp->conn = pjmedia_sdp_conn_clone(pool, m->conn);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001968 }
1969 }
1970
Benny Prijono6ba8c542007-10-16 01:34:14 +00001971 /* Add NAT info in the SDP */
1972 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1973 pjmedia_sdp_attr *a;
1974 pj_str_t value;
1975 char nat_info[80];
1976
1977 value.ptr = nat_info;
1978 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1979 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1980 "%d", pjsua_var.nat_type);
1981 } else {
1982 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1983 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1984 "%d %s",
1985 pjsua_var.nat_type,
1986 type_name);
1987 }
1988
1989 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1990
1991 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1992
1993 }
1994
Benny Prijonoc97608e2007-03-23 16:34:20 +00001995
Benny Prijono0bc99a92011-03-17 04:34:43 +00001996#if DISABLED_FOR_TICKET_1185 && defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001997 /* Check if SRTP is in optional mode and configured to use duplicated
1998 * media, i.e: secured and unsecured version, in the SDP offer.
1999 */
2000 if (!rem_sdp &&
2001 pjsua_var.acc[call->acc_id].cfg.use_srtp == PJMEDIA_SRTP_OPTIONAL &&
2002 pjsua_var.acc[call->acc_id].cfg.srtp_optional_dup_offer)
2003 {
2004 unsigned i;
2005
2006 for (i = 0; i < sdp->media_count; ++i) {
2007 pjmedia_sdp_media *m = sdp->media[i];
2008
Benny Prijono0bc99a92011-03-17 04:34:43 +00002009 /* Check if this media is unsecured but has SDP "crypto"
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002010 * attribute.
2011 */
2012 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") == 0 &&
2013 pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL)
2014 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002015 if (i == (unsigned)call->audio_idx &&
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002016 sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE)
2017 {
2018 /* This is a session update, and peer has chosen the
2019 * unsecured version, so let's make this unsecured too.
2020 */
2021 pjmedia_sdp_media_remove_all_attr(m, "crypto");
2022 } else {
2023 /* This is new offer, duplicate media so we'll have
2024 * secured (with "RTP/SAVP" transport) and and unsecured
2025 * versions.
2026 */
2027 pjmedia_sdp_media *new_m;
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002028
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002029 /* Duplicate this media and apply secured transport */
2030 new_m = pjmedia_sdp_media_clone(pool, m);
2031 pj_strdup2(pool, &new_m->desc.transport, "RTP/SAVP");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002032
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002033 /* Remove the "crypto" attribute in the unsecured media */
2034 pjmedia_sdp_media_remove_all_attr(m, "crypto");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002035
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002036 /* Insert the new media before the unsecured media */
2037 if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002038 pj_array_insert(sdp->media, sizeof(new_m),
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00002039 sdp->media_count, i, &new_m);
2040 ++sdp->media_count;
2041 ++i;
2042 }
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00002043 }
2044 }
2045 }
2046 }
2047#endif
2048
Benny Prijonoc97608e2007-03-23 16:34:20 +00002049 *p_sdp = sdp;
2050 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002051}
2052
2053
2054static void stop_media_session(pjsua_call_id call_id)
2055{
2056 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002057 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002058
Benny Prijonob90fd382011-09-18 14:59:56 +00002059 pj_log_push_indent();
2060
Benny Prijono0bc99a92011-03-17 04:34:43 +00002061 for (mi=0; mi<call->med_cnt; ++mi) {
2062 pjsua_call_media *call_med = &call->media[mi];
2063
2064 if (call_med->type == PJMEDIA_TYPE_AUDIO) {
2065 pjmedia_stream *strm = call_med->strm.a.stream;
2066 pjmedia_rtcp_stat stat;
2067
2068 if (strm) {
2069 if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) {
2070 if (pjsua_var.mconf) {
2071 pjsua_conf_remove_port(call_med->strm.a.conf_slot);
2072 }
2073 call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
2074 }
2075
2076 if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
2077 (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS))
2078 {
2079 /* Save RTP timestamp & sequence, so when media session is
2080 * restarted, those values will be restored as the initial
2081 * RTP timestamp & sequence of the new media session. So in
2082 * the same call session, RTP timestamp and sequence are
2083 * guaranteed to be contigue.
2084 */
2085 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
2086 call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
2087 call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
2088 }
2089
2090 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
2091 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, strm, mi);
2092 }
2093
2094 pjmedia_stream_destroy(strm);
2095 call_med->strm.a.stream = NULL;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002096 }
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00002097 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002098
2099#if PJMEDIA_HAS_VIDEO
2100 else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
Nanang Izzuddin62053a62011-07-12 11:08:32 +00002101 stop_video_stream(call_med);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002102 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002103#endif
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002104
2105 PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed",
2106 call_id, mi));
Benny Prijono0bc99a92011-03-17 04:34:43 +00002107 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002108 }
Benny Prijonob90fd382011-09-18 14:59:56 +00002109
2110 pj_log_pop_indent();
Benny Prijonoc97608e2007-03-23 16:34:20 +00002111}
2112
2113pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
2114{
2115 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002116 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002117
Sauw Ming903154f2011-10-03 08:22:48 +00002118 PJSUA_LOCK();
Sauw Mingec765352011-10-03 02:04:36 +00002119 for (mi=0; mi<call->med_cnt; ++mi) {
2120 pjsua_call_media *call_med = &call->media[mi];
2121
Sauw Ming903154f2011-10-03 08:22:48 +00002122 if (call_med->tp_st == PJSUA_MED_TP_CREATING) {
2123 /* We will do the deinitialization after media transport
2124 * creation is completed.
2125 */
2126 call->async_call.med_ch_deinit = PJ_TRUE;
2127 PJSUA_UNLOCK();
2128 return PJ_SUCCESS;
2129 }
Sauw Mingec765352011-10-03 02:04:36 +00002130 }
Sauw Ming903154f2011-10-03 08:22:48 +00002131 PJSUA_UNLOCK();
Sauw Mingec765352011-10-03 02:04:36 +00002132
Benny Prijonob90fd382011-09-18 14:59:56 +00002133 PJ_LOG(4,(THIS_FILE, "Call %d: deinitializing media..", call_id));
2134 pj_log_push_indent();
2135
Benny Prijonoc97608e2007-03-23 16:34:20 +00002136 stop_media_session(call_id);
2137
Benny Prijono0bc99a92011-03-17 04:34:43 +00002138 for (mi=0; mi<call->med_cnt; ++mi) {
2139 pjsua_call_media *call_med = &call->media[mi];
Benny Prijonoc97608e2007-03-23 16:34:20 +00002140
Sauw Ming73ecfe82011-09-21 10:20:01 +00002141 if (call_med->tp_st > PJSUA_MED_TP_IDLE) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002142 pjmedia_transport_media_stop(call_med->tp);
Sauw Ming73ecfe82011-09-21 10:20:01 +00002143 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002144 }
2145
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002146 //if (call_med->tp_orig && call_med->tp &&
2147 // call_med->tp != call_med->tp_orig)
2148 //{
2149 // pjmedia_transport_close(call_med->tp);
2150 // call_med->tp = call_med->tp_orig;
2151 //}
2152 if (call_med->tp) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002153 pjmedia_transport_close(call_med->tp);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002154 call_med->tp = call_med->tp_orig = NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002155 }
Benny Prijonod8179652008-01-23 20:39:07 +00002156 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00002157
2158 check_snd_dev_idle();
Benny Prijonob90fd382011-09-18 14:59:56 +00002159 pj_log_pop_indent();
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00002160
Benny Prijonoc97608e2007-03-23 16:34:20 +00002161 return PJ_SUCCESS;
2162}
2163
2164
2165/*
2166 * DTMF callback from the stream.
2167 */
2168static void dtmf_callback(pjmedia_stream *strm, void *user_data,
2169 int digit)
2170{
2171 PJ_UNUSED_ARG(strm);
2172
Benny Prijonob90fd382011-09-18 14:59:56 +00002173 pj_log_push_indent();
2174
Benny Prijono0c068262008-02-14 14:38:52 +00002175 /* For discussions about call mutex protection related to this
2176 * callback, please see ticket #460:
2177 * http://trac.pjsip.org/repos/ticket/460#comment:4
2178 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00002179 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
2180 pjsua_call_id call_id;
2181
Benny Prijonod8179652008-01-23 20:39:07 +00002182 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002183 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
2184 }
Benny Prijonob90fd382011-09-18 14:59:56 +00002185
2186 pj_log_pop_indent();
Benny Prijonoc97608e2007-03-23 16:34:20 +00002187}
2188
2189
Benny Prijono0bc99a92011-03-17 04:34:43 +00002190static pj_status_t audio_channel_update(pjsua_call_media *call_med,
2191 pj_pool_t *tmp_pool,
2192 const pjmedia_sdp_session *local_sdp,
2193 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00002194{
Benny Prijono0bc99a92011-03-17 04:34:43 +00002195 pjsua_call *call = call_med->call;
2196 pjmedia_stream_info the_si, *si = &the_si;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002197 pjmedia_port *media_port;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002198 unsigned strm_idx = call_med->idx;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002199 pj_status_t status;
Benny Prijonob90fd382011-09-18 14:59:56 +00002200
2201 PJ_LOG(4,(THIS_FILE,"Audio channel update.."));
2202 pj_log_push_indent();
Benny Prijono0bc99a92011-03-17 04:34:43 +00002203
2204 status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
2205 local_sdp, remote_sdp, strm_idx);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002206 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002207 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002208
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002209 /* Check if no media is active */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002210 if (si->dir == PJMEDIA_DIR_NONE) {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002211 /* Call media state */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002212 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002213
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002214 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002215 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijono68f9e4f2008-03-21 08:56:02 +00002216
Benny Prijonoc97608e2007-03-23 16:34:20 +00002217 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002218 pjmedia_transport_info tp_info;
2219
Benny Prijono224b4e22008-06-19 14:10:28 +00002220 /* Start/restart media transport */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002221 status = pjmedia_transport_media_start(call_med->tp,
2222 tmp_pool, local_sdp,
2223 remote_sdp, strm_idx);
Benny Prijonod8179652008-01-23 20:39:07 +00002224 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002225 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002226
Sauw Ming73ecfe82011-09-21 10:20:01 +00002227 set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING);
Benny Prijono224b4e22008-06-19 14:10:28 +00002228
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002229 /* Get remote SRTP usage policy */
2230 pjmedia_transport_info_init(&tp_info);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002231 pjmedia_transport_get_info(call_med->tp, &tp_info);
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002232 if (tp_info.specific_info_cnt > 0) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +00002233 unsigned i;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002234 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
2235 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
2236 {
2237 pjmedia_srtp_info *srtp_info =
2238 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
2239
Benny Prijono0bc99a92011-03-17 04:34:43 +00002240 call_med->rem_srtp_use = srtp_info->peer_use;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00002241 break;
2242 }
2243 }
2244 }
2245
Benny Prijonoc97608e2007-03-23 16:34:20 +00002246 /* Override ptime, if this option is specified. */
2247 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00002248 si->param->setting.frm_per_pkt = (pj_uint8_t)
2249 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
2250 if (si->param->setting.frm_per_pkt == 0)
2251 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002252 }
2253
2254 /* Disable VAD, if this option is specified. */
2255 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00002256 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002257 }
2258
2259
2260 /* Optionally, application may modify other stream settings here
2261 * (such as jitter buffer parameters, codec ptime, etc.)
2262 */
Benny Prijono91e567e2007-12-28 08:51:58 +00002263 si->jb_init = pjsua_var.media_cfg.jb_init;
2264 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
2265 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
2266 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002267
Benny Prijono8147f402007-11-21 14:50:07 +00002268 /* Set SSRC */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002269 si->ssrc = call_med->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00002270
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002271 /* Set RTP timestamp & sequence, normally these value are intialized
2272 * automatically when stream session created, but for some cases (e.g:
2273 * call reinvite, call update) timestamp and sequence need to be kept
2274 * contigue.
2275 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002276 si->rtp_ts = call_med->rtp_tx_ts;
2277 si->rtp_seq = call_med->rtp_tx_seq;
2278 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002279
Nanang Izzuddin5e39a2b2010-09-20 06:13:02 +00002280#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
2281 /* Enable/disable stream keep-alive and NAT hole punch. */
2282 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
2283#endif
2284
Benny Prijonoc97608e2007-03-23 16:34:20 +00002285 /* Create session based on session info. */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002286 status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,
2287 call_med->tp, NULL,
2288 &call_med->strm.a.stream);
2289 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002290 goto on_return;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002291 }
2292
2293 /* Start stream */
2294 status = pjmedia_stream_start(call_med->strm.a.stream);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002295 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002296 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002297 }
2298
2299 /* If DTMF callback is installed by application, install our
2300 * callback to the session.
2301 */
2302 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002303 pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
2304 &dtmf_callback,
2305 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00002306 }
2307
2308 /* Get the port interface of the first stream in the session.
2309 * We need the port interface to add to the conference bridge.
2310 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002311 pjmedia_stream_get_port(call_med->strm.a.stream, &media_port);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002312
Benny Prijonofc13bf62008-02-20 08:56:15 +00002313 /* Notify application about stream creation.
2314 * Note: application may modify media_port to point to different
2315 * media port
2316 */
2317 if (pjsua_var.ua_cfg.cb.on_stream_created) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002318 pjsua_var.ua_cfg.cb.on_stream_created(call->index,
2319 call_med->strm.a.stream,
2320 strm_idx, &media_port);
Benny Prijonofc13bf62008-02-20 08:56:15 +00002321 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002322
2323 /*
2324 * Add the call to conference bridge.
2325 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002326 {
2327 char tmp[PJSIP_MAX_URL_SIZE];
2328 pj_str_t port_name;
2329
2330 port_name.ptr = tmp;
2331 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
2332 call->inv->dlg->remote.info->uri,
2333 tmp, sizeof(tmp));
2334 if (port_name.slen < 1) {
2335 port_name = pj_str("call");
2336 }
Benny Prijono40d62b62009-08-12 17:53:47 +00002337 status = pjmedia_conf_add_port( pjsua_var.mconf,
2338 call->inv->pool_prov,
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002339 media_port,
2340 &port_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +00002341 (unsigned*)
2342 &call_med->strm.a.conf_slot);
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002343 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002344 goto on_return;
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002345 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002346 }
2347
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002348 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002349 call_med->dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002350
2351 /* Call media state */
2352 if (call->local_hold)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002353 call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
2354 else if (call_med->dir == PJMEDIA_DIR_DECODING)
2355 call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002356 else
Benny Prijono0bc99a92011-03-17 04:34:43 +00002357 call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002358 }
2359
2360 /* Print info. */
2361 {
2362 char info[80];
2363 int info_len = 0;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002364 int len;
2365 const char *dir;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002366
Benny Prijono0bc99a92011-03-17 04:34:43 +00002367 switch (si->dir) {
2368 case PJMEDIA_DIR_NONE:
2369 dir = "inactive";
2370 break;
2371 case PJMEDIA_DIR_ENCODING:
2372 dir = "sendonly";
2373 break;
2374 case PJMEDIA_DIR_DECODING:
2375 dir = "recvonly";
2376 break;
2377 case PJMEDIA_DIR_ENCODING_DECODING:
2378 dir = "sendrecv";
2379 break;
2380 default:
2381 dir = "unknown";
2382 break;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002383 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00002384 len = pj_ansi_sprintf( info+info_len,
2385 ", stream #%d: %.*s (%s)", strm_idx,
2386 (int)si->fmt.encoding_name.slen,
2387 si->fmt.encoding_name.ptr,
2388 dir);
2389 if (len > 0)
2390 info_len += len;
Benny Prijonob90fd382011-09-18 14:59:56 +00002391 PJ_LOG(4,(THIS_FILE,"Audio updated%s", info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00002392 }
2393
Benny Prijonob90fd382011-09-18 14:59:56 +00002394on_return:
2395 pj_log_pop_indent();
2396 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002397}
2398
Benny Prijono0bc99a92011-03-17 04:34:43 +00002399pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
2400 const pjmedia_sdp_session *local_sdp,
2401 const pjmedia_sdp_session *remote_sdp)
2402{
2403 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002404 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002405 pj_pool_t *tmp_pool = call->inv->pool_prov;
2406 unsigned mi;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002407 pj_bool_t got_media = PJ_FALSE;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002408 pj_status_t status = PJ_SUCCESS;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002409
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002410 const pj_str_t STR_AUDIO = { "audio", 5 };
2411 const pj_str_t STR_VIDEO = { "video", 5 };
2412 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
2413 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
2414 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
2415 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
2416 pj_bool_t need_renego_sdp = PJ_FALSE;
2417
Benny Prijono0bc99a92011-03-17 04:34:43 +00002418 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
2419 return PJ_EBUSY;
2420
Benny Prijonob90fd382011-09-18 14:59:56 +00002421 PJ_LOG(4,(THIS_FILE, "Call %d: updating media..", call_id));
2422 pj_log_push_indent();
2423
Benny Prijono0bc99a92011-03-17 04:34:43 +00002424 /* Destroy existing media session, if any. */
2425 stop_media_session(call->index);
2426
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002427 /* Call media count must be at least equal to SDP media. Note that
2428 * it may not be equal when remote removed any SDP media line.
2429 */
2430 pj_assert(call->med_cnt >= local_sdp->media_count);
2431
Benny Prijono0bc99a92011-03-17 04:34:43 +00002432 /* Reset audio_idx first */
2433 call->audio_idx = -1;
2434
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002435 /* Apply maximum audio/video count of the account */
2436 sort_media(local_sdp, &STR_AUDIO, acc->cfg.use_srtp,
2437 maudidx, &maudcnt);
2438#if PJMEDIA_HAS_VIDEO
2439 sort_media(local_sdp, &STR_VIDEO, acc->cfg.use_srtp,
2440 mvididx, &mvidcnt);
2441#else
2442 PJ_UNUSED_ARG(STR_VIDEO);
2443 mvidcnt = 0;
2444#endif
2445 if (maudcnt > acc->cfg.max_audio_cnt || mvidcnt > acc->cfg.max_video_cnt)
2446 {
2447 pjmedia_sdp_session *local_sdp2;
2448
2449 maudcnt = PJ_MIN(maudcnt, acc->cfg.max_audio_cnt);
2450 mvidcnt = PJ_MIN(mvidcnt, acc->cfg.max_video_cnt);
2451 local_sdp2 = pjmedia_sdp_session_clone(tmp_pool, local_sdp);
2452
2453 for (mi=0; mi < local_sdp2->media_count; ++mi) {
2454 pjmedia_sdp_media *m = local_sdp2->media[mi];
2455
2456 if (m->desc.port == 0 ||
2457 pj_memchr(maudidx, mi, maudcnt*sizeof(maudidx[0])) ||
2458 pj_memchr(mvididx, mi, mvidcnt*sizeof(mvididx[0])))
2459 {
2460 continue;
2461 }
2462
2463 /* Deactivate this media */
2464 pjmedia_sdp_media_deactivate(tmp_pool, m);
2465 }
2466
2467 local_sdp = local_sdp2;
2468 need_renego_sdp = PJ_TRUE;
2469 }
2470
Benny Prijono0bc99a92011-03-17 04:34:43 +00002471 /* Process each media stream */
2472 for (mi=0; mi < call->med_cnt; ++mi) {
2473 pjsua_call_media *call_med = &call->media[mi];
2474
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002475 if (mi >= local_sdp->media_count ||
2476 mi >= remote_sdp->media_count)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002477 {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002478 /* This may happen when remote removed any SDP media lines in
2479 * its re-offer.
2480 */
2481 continue;
2482#if 0
Benny Prijono0bc99a92011-03-17 04:34:43 +00002483 /* Something is wrong */
2484 PJ_LOG(1,(THIS_FILE, "Error updating media for call %d: "
2485 "invalid media index %d in SDP", call_id, mi));
Benny Prijonob90fd382011-09-18 14:59:56 +00002486 status = PJMEDIA_SDP_EINSDP;
2487 goto on_error;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002488#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002489 }
2490
2491 switch (call_med->type) {
2492 case PJMEDIA_TYPE_AUDIO:
2493 status = audio_channel_update(call_med, tmp_pool,
2494 local_sdp, remote_sdp);
2495 if (call->audio_idx==-1 && status==PJ_SUCCESS &&
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002496 call_med->strm.a.stream)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002497 {
2498 call->audio_idx = mi;
2499 }
2500 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00002501#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002502 case PJMEDIA_TYPE_VIDEO:
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002503 status = video_channel_update(call_med, tmp_pool,
2504 local_sdp, remote_sdp);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002505 break;
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002506#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002507 default:
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002508 status = PJMEDIA_EINVALIMEDIATYPE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002509 break;
2510 }
2511
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002512 /* Close the transport of deactivated media, need this here as media
2513 * can be deactivated by the SDP negotiation and the max media count
2514 * (account) setting.
2515 */
2516 if (local_sdp->media[mi]->desc.port==0 && call_med->tp) {
2517 pjmedia_transport_close(call_med->tp);
2518 call_med->tp = call_med->tp_orig = NULL;
Sauw Ming73ecfe82011-09-21 10:20:01 +00002519 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002520 }
2521
Benny Prijono0bc99a92011-03-17 04:34:43 +00002522 if (status != PJ_SUCCESS) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002523 PJ_PERROR(1,(THIS_FILE, status, "Error updating media call%02d:%d",
Benny Prijono0bc99a92011-03-17 04:34:43 +00002524 call_id, mi));
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002525 } else {
2526 got_media = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002527 }
2528 }
2529
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002530 /* Perform SDP re-negotiation if needed. */
2531 if (got_media && need_renego_sdp) {
2532 pjmedia_sdp_neg *neg = call->inv->neg;
2533
2534 /* This should only happen when we are the answerer. */
2535 PJ_ASSERT_RETURN(neg && !pjmedia_sdp_neg_was_answer_remote(neg),
2536 PJMEDIA_SDPNEG_EINSTATE);
2537
2538 status = pjmedia_sdp_neg_set_remote_offer(tmp_pool, neg, remote_sdp);
2539 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002540 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002541
2542 status = pjmedia_sdp_neg_set_local_answer(tmp_pool, neg, local_sdp);
2543 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002544 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002545
2546 status = pjmedia_sdp_neg_negotiate(tmp_pool, neg, 0);
2547 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002548 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002549 }
2550
Benny Prijonob90fd382011-09-18 14:59:56 +00002551 pj_log_pop_indent();
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002552 return (got_media? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA);
Benny Prijonob90fd382011-09-18 14:59:56 +00002553
2554on_error:
2555 pj_log_pop_indent();
2556 return status;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002557}
2558
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002559/*
2560 * Get maxinum number of conference ports.
2561 */
2562PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
2563{
2564 return pjsua_var.media_cfg.max_media_ports;
2565}
2566
2567
2568/*
2569 * Get current number of active ports in the bridge.
2570 */
2571PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
2572{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002573 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002574 unsigned count = PJ_ARRAY_SIZE(ports);
2575 pj_status_t status;
2576
2577 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
2578 if (status != PJ_SUCCESS)
2579 count = 0;
2580
2581 return count;
2582}
2583
2584
2585/*
2586 * Enumerate all conference ports.
2587 */
2588PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
2589 unsigned *count)
2590{
2591 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
2592}
2593
2594
2595/*
2596 * Get information about the specified conference port
2597 */
2598PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
2599 pjsua_conf_port_info *info)
2600{
2601 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00002602 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002603 pj_status_t status;
2604
2605 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
2606 if (status != PJ_SUCCESS)
2607 return status;
2608
Benny Prijonoac623b32006-07-03 15:19:31 +00002609 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002610 info->slot_id = id;
2611 info->name = cinfo.name;
2612 info->clock_rate = cinfo.clock_rate;
2613 info->channel_count = cinfo.channel_count;
2614 info->samples_per_frame = cinfo.samples_per_frame;
2615 info->bits_per_sample = cinfo.bits_per_sample;
2616
2617 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00002618 info->listener_cnt = cinfo.listener_cnt;
2619 for (i=0; i<cinfo.listener_cnt; ++i) {
2620 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002621 }
2622
2623 return PJ_SUCCESS;
2624}
2625
2626
2627/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00002628 * Add arbitrary media port to PJSUA's conference bridge.
2629 */
2630PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
2631 pjmedia_port *port,
2632 pjsua_conf_port_id *p_id)
2633{
2634 pj_status_t status;
2635
2636 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
2637 port, NULL, (unsigned*)p_id);
2638 if (status != PJ_SUCCESS) {
2639 if (p_id)
2640 *p_id = PJSUA_INVALID_ID;
2641 }
2642
2643 return status;
2644}
2645
2646
2647/*
2648 * Remove arbitrary slot from the conference bridge.
2649 */
2650PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
2651{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002652 pj_status_t status;
2653
2654 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
2655 check_snd_dev_idle();
2656
2657 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00002658}
2659
2660
2661/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002662 * Establish unidirectional media flow from souce to sink.
2663 */
2664PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
2665 pjsua_conf_port_id sink)
2666{
Benny Prijonob90fd382011-09-18 14:59:56 +00002667 pj_status_t status = PJ_SUCCESS;
2668
2669 PJ_LOG(4,(THIS_FILE, "%s connect: %d --> %d",
2670 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
2671 source, sink));
2672 pj_log_push_indent();
2673
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002674 /* If sound device idle timer is active, cancel it first. */
Benny Prijono0f711b42009-05-06 19:08:43 +00002675 PJSUA_LOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002676 if (pjsua_var.snd_idle_timer.id) {
2677 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
2678 pjsua_var.snd_idle_timer.id = PJ_FALSE;
2679 }
Benny Prijono0f711b42009-05-06 19:08:43 +00002680 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002681
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002682
Benny Prijonof798e502009-03-09 13:08:16 +00002683 /* For audio switchboard (i.e. APS-Direct):
2684 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002685 * (format, clock rate, channel count) must match to peer's.
2686 * Note that sound device can be reopened only if it doesn't have
2687 * any connection.
2688 */
Benny Prijonof798e502009-03-09 13:08:16 +00002689 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002690 pjmedia_conf_port_info port0_info;
2691 pjmedia_conf_port_info peer_info;
2692 unsigned peer_id;
2693 pj_bool_t need_reopen = PJ_FALSE;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002694
2695 peer_id = (source!=0)? source : sink;
2696 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
2697 &peer_info);
2698 pj_assert(status == PJ_SUCCESS);
2699
2700 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
2701 pj_assert(status == PJ_SUCCESS);
2702
2703 /* Check if sound device is instantiated. */
2704 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2705 !pjsua_var.no_snd);
2706
2707 /* Check if sound device need to reopen because it needs to modify
2708 * settings to match its peer. Sound device must be idle in this case
2709 * though.
2710 */
2711 if (!need_reopen &&
2712 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
2713 {
2714 need_reopen = (peer_info.format.id != port0_info.format.id ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002715 peer_info.format.det.aud.avg_bps !=
2716 port0_info.format.det.aud.avg_bps ||
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002717 peer_info.clock_rate != port0_info.clock_rate ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002718 peer_info.channel_count!=port0_info.channel_count);
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002719 }
2720
2721 if (need_reopen) {
Benny Prijonod65f78c2009-06-03 18:59:37 +00002722 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
Sauw Ming98766c72011-03-11 06:57:24 +00002723 pjmedia_snd_port_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002724
Benny Prijonod65f78c2009-06-03 18:59:37 +00002725 /* Create parameter based on peer info */
Sauw Ming98766c72011-03-11 06:57:24 +00002726 status = create_aud_param(&param.base, pjsua_var.cap_dev,
Benny Prijonod65f78c2009-06-03 18:59:37 +00002727 pjsua_var.play_dev,
2728 peer_info.clock_rate,
2729 peer_info.channel_count,
2730 peer_info.samples_per_frame,
2731 peer_info.bits_per_sample);
2732 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002733 pjsua_perror(THIS_FILE, "Error opening sound device",
2734 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002735 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002736 }
Benny Prijonof798e502009-03-09 13:08:16 +00002737
Benny Prijonod65f78c2009-06-03 18:59:37 +00002738 /* And peer format */
2739 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
Sauw Ming98766c72011-03-11 06:57:24 +00002740 param.base.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
2741 param.base.ext_fmt = peer_info.format;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002742 }
Benny Prijono10454dc2009-02-21 14:21:59 +00002743
Sauw Ming98766c72011-03-11 06:57:24 +00002744 param.options = 0;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002745 status = open_snd_dev(&param);
2746 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002747 pjsua_perror(THIS_FILE, "Error opening sound device",
2748 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002749 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002750 }
2751 } else {
2752 /* Null-audio */
Benny Prijonoc45d9512010-12-10 11:04:30 +00002753 status = pjsua_set_snd_dev(pjsua_var.cap_dev,
2754 pjsua_var.play_dev);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002755 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002756 pjsua_perror(THIS_FILE, "Error opening sound device",
2757 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002758 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002759 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002760 }
Benny Prijono2d647722011-07-13 03:05:22 +00002761 } else if (pjsua_var.no_snd) {
2762 if (!pjsua_var.snd_is_on) {
2763 pjsua_var.snd_is_on = PJ_TRUE;
2764 /* Notify app */
2765 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2766 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2767 }
2768 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002769 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002770
Benny Prijonof798e502009-03-09 13:08:16 +00002771 } else {
2772 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002773
Benny Prijonof798e502009-03-09 13:08:16 +00002774 /* Create sound port if none is instantiated */
2775 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2776 !pjsua_var.no_snd)
2777 {
2778 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002779
Benny Prijonof798e502009-03-09 13:08:16 +00002780 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
2781 if (status != PJ_SUCCESS) {
2782 pjsua_perror(THIS_FILE, "Error opening sound device", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002783 goto on_return;
Benny Prijonof798e502009-03-09 13:08:16 +00002784 }
Benny Prijono2d647722011-07-13 03:05:22 +00002785 } else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) {
2786 pjsua_var.snd_is_on = PJ_TRUE;
2787 /* Notify app */
2788 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2789 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2790 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002791 }
Benny Prijonof798e502009-03-09 13:08:16 +00002792 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002793
Benny Prijonob90fd382011-09-18 14:59:56 +00002794 status = pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
2795
2796on_return:
2797 pj_log_pop_indent();
2798 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002799}
2800
2801
2802/*
2803 * Disconnect media flow from the source to destination port.
2804 */
2805PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
2806 pjsua_conf_port_id sink)
2807{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002808 pj_status_t status;
2809
Benny Prijonob90fd382011-09-18 14:59:56 +00002810 PJ_LOG(4,(THIS_FILE, "%s disconnect: %d -x- %d",
2811 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
2812 source, sink));
2813 pj_log_push_indent();
2814
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002815 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002816 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002817
Benny Prijonob90fd382011-09-18 14:59:56 +00002818 pj_log_pop_indent();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002819 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002820}
2821
2822
Benny Prijono6dd967c2006-12-26 02:27:14 +00002823/*
2824 * Adjust the signal level to be transmitted from the bridge to the
2825 * specified port by making it louder or quieter.
2826 */
2827PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
2828 float level)
2829{
2830 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
2831 (int)((level-1) * 128));
2832}
2833
2834/*
2835 * Adjust the signal level to be received from the specified port (to
2836 * the bridge) by making it louder or quieter.
2837 */
2838PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
2839 float level)
2840{
2841 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
2842 (int)((level-1) * 128));
2843}
2844
2845
2846/*
2847 * Get last signal level transmitted to or received from the specified port.
2848 */
2849PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
2850 unsigned *tx_level,
2851 unsigned *rx_level)
2852{
2853 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
2854 tx_level, rx_level);
2855}
2856
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002857/*****************************************************************************
2858 * File player.
2859 */
2860
Benny Prijonod5696da2007-07-17 16:25:45 +00002861static char* get_basename(const char *path, unsigned len)
2862{
2863 char *p = ((char*)path) + len;
2864
2865 if (len==0)
2866 return p;
2867
Benny Prijono1f61a8f2007-08-16 10:11:44 +00002868 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00002869
2870 return (p==path) ? p : p+1;
2871}
2872
2873
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002874/*
2875 * Create a file player, and automatically connect this player to
2876 * the conference bridge.
2877 */
2878PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
2879 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002880 pjsua_player_id *p_id)
2881{
2882 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002883 char path[PJ_MAXPATH];
Benny Prijonob90fd382011-09-18 14:59:56 +00002884 pj_pool_t *pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002885 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00002886 pj_status_t status = PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002887
2888 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2889 return PJ_ETOOMANY;
2890
Benny Prijonob90fd382011-09-18 14:59:56 +00002891 PJ_LOG(4,(THIS_FILE, "Creating file player: %.*s..",
2892 (int)filename->slen, filename->ptr));
2893 pj_log_push_indent();
2894
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002895 PJSUA_LOCK();
2896
2897 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2898 if (pjsua_var.player[file_id].port == NULL)
2899 break;
2900 }
2901
2902 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2903 /* This is unexpected */
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002904 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00002905 status = PJ_EBUG;
2906 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002907 }
2908
2909 pj_memcpy(path, filename->ptr, filename->slen);
2910 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00002911
2912 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2913 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002914 status = PJ_ENOMEM;
2915 goto on_error;
Benny Prijonod5696da2007-07-17 16:25:45 +00002916 }
2917
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00002918 status = pjmedia_wav_player_port_create(
2919 pool, path,
2920 pjsua_var.mconf_cfg.samples_per_frame *
Benny Prijonoc45d9512010-12-10 11:04:30 +00002921 1000 / pjsua_var.media_cfg.channel_count /
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00002922 pjsua_var.media_cfg.clock_rate,
2923 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002924 if (status != PJ_SUCCESS) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002925 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002926 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002927 }
2928
Benny Prijono5297af92008-03-18 13:40:40 +00002929 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002930 port, filename, &slot);
2931 if (status != PJ_SUCCESS) {
2932 pjmedia_port_destroy(port);
Benny Prijono32e4f492007-01-24 00:44:26 +00002933 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
2934 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002935 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002936 }
2937
Benny Prijonoa66c3312007-01-21 23:12:40 +00002938 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00002939 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002940 pjsua_var.player[file_id].port = port;
2941 pjsua_var.player[file_id].slot = slot;
2942
2943 if (p_id) *p_id = file_id;
2944
2945 ++pjsua_var.player_cnt;
2946
2947 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00002948
2949 PJ_LOG(4,(THIS_FILE, "Player created, id=%d, slot=%d", file_id, slot));
2950
2951 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002952 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00002953
2954on_error:
2955 PJSUA_UNLOCK();
2956 if (pool) pj_pool_release(pool);
2957 pj_log_pop_indent();
2958 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002959}
2960
2961
2962/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00002963 * Create a file playlist media port, and automatically add the port
2964 * to the conference bridge.
2965 */
2966PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
2967 unsigned file_count,
2968 const pj_str_t *label,
2969 unsigned options,
2970 pjsua_player_id *p_id)
2971{
2972 unsigned slot, file_id, ptime;
Benny Prijonob90fd382011-09-18 14:59:56 +00002973 pj_pool_t *pool = NULL;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002974 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00002975 pj_status_t status = PJ_SUCCESS;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002976
2977 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2978 return PJ_ETOOMANY;
2979
Benny Prijonob90fd382011-09-18 14:59:56 +00002980 PJ_LOG(4,(THIS_FILE, "Creating playlist with %d file(s)..", file_count));
2981 pj_log_push_indent();
2982
Benny Prijonoa66c3312007-01-21 23:12:40 +00002983 PJSUA_LOCK();
2984
2985 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2986 if (pjsua_var.player[file_id].port == NULL)
2987 break;
2988 }
2989
2990 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2991 /* This is unexpected */
Benny Prijonoa66c3312007-01-21 23:12:40 +00002992 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00002993 status = PJ_EBUG;
2994 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002995 }
2996
2997
2998 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
2999 pjsua_var.media_cfg.clock_rate;
3000
Benny Prijonod5696da2007-07-17 16:25:45 +00003001 pool = pjsua_pool_create("playlist", 1000, 1000);
3002 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003003 status = PJ_ENOMEM;
3004 goto on_error;
Benny Prijonod5696da2007-07-17 16:25:45 +00003005 }
3006
3007 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00003008 file_names, file_count,
3009 ptime, options, 0, &port);
3010 if (status != PJ_SUCCESS) {
Benny Prijonoa66c3312007-01-21 23:12:40 +00003011 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003012 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003013 }
3014
Benny Prijonod5696da2007-07-17 16:25:45 +00003015 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00003016 port, &port->info.name, &slot);
3017 if (status != PJ_SUCCESS) {
3018 pjmedia_port_destroy(port);
Benny Prijonoa66c3312007-01-21 23:12:40 +00003019 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003020 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003021 }
3022
3023 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00003024 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003025 pjsua_var.player[file_id].port = port;
3026 pjsua_var.player[file_id].slot = slot;
3027
3028 if (p_id) *p_id = file_id;
3029
3030 ++pjsua_var.player_cnt;
3031
3032 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003033
3034 PJ_LOG(4,(THIS_FILE, "Playlist created, id=%d, slot=%d", file_id, slot));
3035
3036 pj_log_pop_indent();
3037
Benny Prijonoa66c3312007-01-21 23:12:40 +00003038 return PJ_SUCCESS;
3039
Benny Prijonob90fd382011-09-18 14:59:56 +00003040on_error:
3041 PJSUA_UNLOCK();
3042 if (pool) pj_pool_release(pool);
3043 pj_log_pop_indent();
3044
3045 return status;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003046}
3047
3048
3049/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003050 * Get conference port ID associated with player.
3051 */
3052PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
3053{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003054 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003055 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
3056
3057 return pjsua_var.player[id].slot;
3058}
3059
Benny Prijono469b1522006-12-26 03:05:17 +00003060/*
3061 * Get the media port for the player.
3062 */
Benny Prijonobe41d862008-01-18 13:24:28 +00003063PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00003064 pjmedia_port **p_port)
3065{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003066 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00003067 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
3068 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
3069
3070 *p_port = pjsua_var.player[id].port;
3071
3072 return PJ_SUCCESS;
3073}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003074
3075/*
3076 * Set playback position.
3077 */
3078PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
3079 pj_uint32_t samples)
3080{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003081 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003082 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00003083 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003084
3085 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
3086}
3087
3088
3089/*
3090 * Close the file, remove the player from the bridge, and free
3091 * resources associated with the file player.
3092 */
3093PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
3094{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003095 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003096 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
3097
Benny Prijonob90fd382011-09-18 14:59:56 +00003098 PJ_LOG(4,(THIS_FILE, "Destroying player %d..", id));
3099 pj_log_push_indent();
3100
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003101 PJSUA_LOCK();
3102
3103 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00003104 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003105 pjmedia_port_destroy(pjsua_var.player[id].port);
3106 pjsua_var.player[id].port = NULL;
3107 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00003108 pj_pool_release(pjsua_var.player[id].pool);
3109 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003110 pjsua_var.player_cnt--;
3111 }
3112
3113 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003114 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003115
3116 return PJ_SUCCESS;
3117}
3118
3119
3120/*****************************************************************************
3121 * File recorder.
3122 */
3123
3124/*
3125 * Create a file recorder, and automatically connect this recorder to
3126 * the conference bridge.
3127 */
3128PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00003129 unsigned enc_type,
3130 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003131 pj_ssize_t max_size,
3132 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003133 pjsua_recorder_id *p_id)
3134{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003135 enum Format
3136 {
3137 FMT_UNKNOWN,
3138 FMT_WAV,
3139 FMT_MP3,
3140 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003141 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00003142 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003143 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00003144 int file_format;
Benny Prijonob90fd382011-09-18 14:59:56 +00003145 pj_pool_t *pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003146 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00003147 pj_status_t status = PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003148
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003149 /* Filename must present */
3150 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
3151
Benny Prijono00cae612006-07-31 15:19:36 +00003152 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003153 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00003154
Benny Prijono8f310522006-10-20 11:08:49 +00003155 /* Don't support encoding type at present */
3156 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00003157
Benny Prijonob90fd382011-09-18 14:59:56 +00003158 PJ_LOG(4,(THIS_FILE, "Creating recorder %.*s..",
3159 (int)filename->slen, filename->ptr));
3160 pj_log_push_indent();
3161
3162 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) {
3163 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003164 return PJ_ETOOMANY;
Benny Prijonob90fd382011-09-18 14:59:56 +00003165 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003166
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003167 /* Determine the file format */
3168 ext.ptr = filename->ptr + filename->slen - 4;
3169 ext.slen = 4;
3170
3171 if (pj_stricmp2(&ext, ".wav") == 0)
3172 file_format = FMT_WAV;
3173 else if (pj_stricmp2(&ext, ".mp3") == 0)
3174 file_format = FMT_MP3;
3175 else {
3176 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
3177 "determine file format for %.*s",
3178 (int)filename->slen, filename->ptr));
Benny Prijonob90fd382011-09-18 14:59:56 +00003179 pj_log_pop_indent();
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003180 return PJ_ENOTSUP;
3181 }
3182
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003183 PJSUA_LOCK();
3184
3185 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
3186 if (pjsua_var.recorder[file_id].port == NULL)
3187 break;
3188 }
3189
3190 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
3191 /* This is unexpected */
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003192 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00003193 status = PJ_EBUG;
3194 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003195 }
3196
3197 pj_memcpy(path, filename->ptr, filename->slen);
3198 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003199
Benny Prijonod5696da2007-07-17 16:25:45 +00003200 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
3201 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003202 status = PJ_ENOMEM;
3203 goto on_return;
Benny Prijonod5696da2007-07-17 16:25:45 +00003204 }
3205
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003206 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00003207 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003208 pjsua_var.media_cfg.clock_rate,
3209 pjsua_var.mconf_cfg.channel_count,
3210 pjsua_var.mconf_cfg.samples_per_frame,
3211 pjsua_var.mconf_cfg.bits_per_sample,
3212 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003213 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00003214 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00003215 port = NULL;
3216 status = PJ_ENOTSUP;
3217 }
3218
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003219 if (status != PJ_SUCCESS) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003220 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003221 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003222 }
3223
Benny Prijonod5696da2007-07-17 16:25:45 +00003224 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003225 port, filename, &slot);
3226 if (status != PJ_SUCCESS) {
3227 pjmedia_port_destroy(port);
Benny Prijonob90fd382011-09-18 14:59:56 +00003228 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003229 }
3230
3231 pjsua_var.recorder[file_id].port = port;
3232 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00003233 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003234
3235 if (p_id) *p_id = file_id;
3236
3237 ++pjsua_var.rec_cnt;
3238
3239 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003240
3241 PJ_LOG(4,(THIS_FILE, "Recorder created, id=%d, slot=%d", file_id, slot));
3242
3243 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003244 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003245
3246on_return:
3247 PJSUA_UNLOCK();
3248 if (pool) pj_pool_release(pool);
3249 pj_log_pop_indent();
3250 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003251}
3252
3253
3254/*
3255 * Get conference port associated with recorder.
3256 */
3257PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
3258{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003259 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3260 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003261 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3262
3263 return pjsua_var.recorder[id].slot;
3264}
3265
Benny Prijono469b1522006-12-26 03:05:17 +00003266/*
3267 * Get the media port for the recorder.
3268 */
3269PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
3270 pjmedia_port **p_port)
3271{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003272 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3273 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00003274 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3275 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
3276
3277 *p_port = pjsua_var.recorder[id].port;
3278 return PJ_SUCCESS;
3279}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003280
3281/*
3282 * Destroy recorder (this will complete recording).
3283 */
3284PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
3285{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003286 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3287 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003288 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3289
Benny Prijonob90fd382011-09-18 14:59:56 +00003290 PJ_LOG(4,(THIS_FILE, "Destroying recorder %d..", id));
3291 pj_log_push_indent();
3292
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003293 PJSUA_LOCK();
3294
3295 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00003296 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003297 pjmedia_port_destroy(pjsua_var.recorder[id].port);
3298 pjsua_var.recorder[id].port = NULL;
3299 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00003300 pj_pool_release(pjsua_var.recorder[id].pool);
3301 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003302 pjsua_var.rec_cnt--;
3303 }
3304
3305 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003306 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003307
3308 return PJ_SUCCESS;
3309}
3310
3311
3312/*****************************************************************************
3313 * Sound devices.
3314 */
3315
3316/*
3317 * Enum sound devices.
3318 */
Benny Prijonof798e502009-03-09 13:08:16 +00003319
3320PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003321 unsigned *count)
3322{
3323 unsigned i, dev_count;
3324
Benny Prijono10454dc2009-02-21 14:21:59 +00003325 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003326
3327 if (dev_count > *count) dev_count = *count;
3328
3329 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00003330 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003331
Benny Prijono10454dc2009-02-21 14:21:59 +00003332 status = pjmedia_aud_dev_get_info(i, &info[i]);
3333 if (status != PJ_SUCCESS)
3334 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003335 }
3336
3337 *count = dev_count;
3338
3339 return PJ_SUCCESS;
3340}
Benny Prijonof798e502009-03-09 13:08:16 +00003341
3342
Benny Prijono10454dc2009-02-21 14:21:59 +00003343PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
3344 unsigned *count)
3345{
3346 unsigned i, dev_count;
3347
3348 dev_count = pjmedia_aud_dev_count();
3349
3350 if (dev_count > *count) dev_count = *count;
3351 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
3352
3353 for (i=0; i<dev_count; ++i) {
3354 pjmedia_aud_dev_info ai;
3355 pj_status_t status;
3356
3357 status = pjmedia_aud_dev_get_info(i, &ai);
3358 if (status != PJ_SUCCESS)
3359 return status;
3360
3361 strncpy(info[i].name, ai.name, sizeof(info[i].name));
3362 info[i].name[sizeof(info[i].name)-1] = '\0';
3363 info[i].input_count = ai.input_count;
3364 info[i].output_count = ai.output_count;
3365 info[i].default_samples_per_sec = ai.default_samples_per_sec;
3366 }
3367
3368 *count = dev_count;
3369
3370 return PJ_SUCCESS;
3371}
Benny Prijono10454dc2009-02-21 14:21:59 +00003372
Benny Prijonof798e502009-03-09 13:08:16 +00003373/* Create audio device parameter to open the device */
3374static pj_status_t create_aud_param(pjmedia_aud_param *param,
3375 pjmedia_aud_dev_index capture_dev,
3376 pjmedia_aud_dev_index playback_dev,
3377 unsigned clock_rate,
3378 unsigned channel_count,
3379 unsigned samples_per_frame,
3380 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003381{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003382 pj_status_t status;
3383
Benny Prijono96e74f32009-02-22 12:00:12 +00003384 /* Normalize device ID with new convention about default device ID */
3385 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
3386 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
3387
Benny Prijono10454dc2009-02-21 14:21:59 +00003388 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00003389 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00003390 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00003391 pjsua_perror(THIS_FILE, "Error retrieving default audio "
3392 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00003393 return status;
3394 }
Benny Prijonof798e502009-03-09 13:08:16 +00003395 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
3396 param->rec_id = capture_dev;
3397 param->play_id = playback_dev;
3398 param->clock_rate = clock_rate;
3399 param->channel_count = channel_count;
3400 param->samples_per_frame = samples_per_frame;
3401 param->bits_per_sample = bits_per_sample;
3402
3403 /* Update the setting with user preference */
3404#define update_param(cap, field) \
3405 if (pjsua_var.aud_param.flags & cap) { \
3406 param->flags |= cap; \
3407 param->field = pjsua_var.aud_param.field; \
3408 }
3409 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3410 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3411 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3412 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3413#undef update_param
3414
Benny Prijono10454dc2009-02-21 14:21:59 +00003415 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00003416 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
3417 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
3418 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
3419 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
3420
Benny Prijono10454dc2009-02-21 14:21:59 +00003421 /* EC settings */
3422 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00003423 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
3424 param->ec_enabled = PJ_TRUE;
3425 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00003426 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00003427 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00003428 }
3429
Benny Prijonof798e502009-03-09 13:08:16 +00003430 return PJ_SUCCESS;
3431}
Benny Prijono26056d82006-10-11 16:03:41 +00003432
Benny Prijonof798e502009-03-09 13:08:16 +00003433/* Internal: the first time the audio device is opened (during app
3434 * startup), retrieve the audio settings such as volume level
3435 * so that aud_get_settings() will work.
3436 */
3437static pj_status_t update_initial_aud_param()
3438{
3439 pjmedia_aud_stream *strm;
3440 pjmedia_aud_param param;
3441 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00003442
Benny Prijonof798e502009-03-09 13:08:16 +00003443 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00003444
Benny Prijonof798e502009-03-09 13:08:16 +00003445 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00003446
Benny Prijonof798e502009-03-09 13:08:16 +00003447 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003448 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00003449 pjsua_perror(THIS_FILE, "Error audio stream "
3450 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003451 return status;
3452 }
3453
Benny Prijonof798e502009-03-09 13:08:16 +00003454#define update_saved_param(cap, field) \
3455 if (param.flags & cap) { \
3456 pjsua_var.aud_param.flags |= cap; \
3457 pjsua_var.aud_param.field = param.field; \
3458 }
3459
3460 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3461 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3462 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3463 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3464#undef update_saved_param
3465
3466 return PJ_SUCCESS;
3467}
3468
3469/* Get format name */
3470static const char *get_fmt_name(pj_uint32_t id)
3471{
3472 static char name[8];
3473
3474 if (id == PJMEDIA_FORMAT_L16)
3475 return "PCM";
3476 pj_memcpy(name, &id, 4);
3477 name[4] = '\0';
3478 return name;
3479}
3480
3481/* Open sound device with the setting. */
Sauw Ming98766c72011-03-11 06:57:24 +00003482static pj_status_t open_snd_dev(pjmedia_snd_port_param *param)
Benny Prijonof798e502009-03-09 13:08:16 +00003483{
3484 pjmedia_port *conf_port;
3485 pj_status_t status;
3486
3487 PJ_ASSERT_RETURN(param, PJ_EINVAL);
3488
3489 /* Check if NULL sound device is used */
Sauw Ming98766c72011-03-11 06:57:24 +00003490 if (NULL_SND_DEV_ID==param->base.rec_id ||
3491 NULL_SND_DEV_ID==param->base.play_id)
3492 {
Benny Prijonof798e502009-03-09 13:08:16 +00003493 return pjsua_set_null_snd_dev();
3494 }
3495
3496 /* Close existing sound port */
3497 close_snd_dev();
3498
Benny Prijono2d647722011-07-13 03:05:22 +00003499 /* Notify app */
3500 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3501 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
3502 }
3503
Benny Prijonof798e502009-03-09 13:08:16 +00003504 /* Create memory pool for sound device. */
3505 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3506 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3507
3508
3509 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
Sauw Ming98766c72011-03-11 06:57:24 +00003510 get_fmt_name(param->base.ext_fmt.id),
3511 param->base.clock_rate, param->base.channel_count,
3512 param->base.samples_per_frame / param->base.channel_count *
3513 1000 / param->base.clock_rate));
Benny Prijonob90fd382011-09-18 14:59:56 +00003514 pj_log_push_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003515
3516 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
Sauw Ming98766c72011-03-11 06:57:24 +00003517 param, &pjsua_var.snd_port);
Benny Prijonof798e502009-03-09 13:08:16 +00003518 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00003519 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003520
3521 /* Get the port0 of the conference bridge. */
3522 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3523 pj_assert(conf_port != NULL);
3524
3525 /* For conference bridge, resample if necessary if the bridge's
3526 * clock rate is different than the sound device's clock rate.
3527 */
3528 if (!pjsua_var.is_mswitch &&
Sauw Ming98766c72011-03-11 06:57:24 +00003529 param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM &&
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003530 PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate)
Benny Prijonof798e502009-03-09 13:08:16 +00003531 {
3532 pjmedia_port *resample_port;
3533 unsigned resample_opt = 0;
3534
3535 if (pjsua_var.media_cfg.quality >= 3 &&
3536 pjsua_var.media_cfg.quality <= 4)
3537 {
3538 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
3539 }
3540 else if (pjsua_var.media_cfg.quality < 3) {
3541 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
3542 }
3543
3544 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
3545 conf_port,
Sauw Ming98766c72011-03-11 06:57:24 +00003546 param->base.clock_rate,
Benny Prijonof798e502009-03-09 13:08:16 +00003547 resample_opt,
3548 &resample_port);
3549 if (status != PJ_SUCCESS) {
3550 char errmsg[PJ_ERR_MSG_SIZE];
3551 pj_strerror(status, errmsg, sizeof(errmsg));
3552 PJ_LOG(4, (THIS_FILE,
3553 "Error creating resample port: %s",
3554 errmsg));
3555 close_snd_dev();
Benny Prijonob90fd382011-09-18 14:59:56 +00003556 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003557 }
3558
3559 conf_port = resample_port;
3560 }
3561
3562 /* Otherwise for audio switchboard, the switch's port0 setting is
3563 * derived from the sound device setting, so update the setting.
3564 */
3565 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003566 pj_memcpy(&conf_port->info.fmt, &param->base.ext_fmt,
Benny Prijonoc45d9512010-12-10 11:04:30 +00003567 sizeof(conf_port->info.fmt));
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003568 conf_port->info.fmt.det.aud.clock_rate = param->base.clock_rate;
3569 conf_port->info.fmt.det.aud.frame_time_usec = param->base.samples_per_frame*
Benny Prijonoc45d9512010-12-10 11:04:30 +00003570 1000000 /
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003571 param->base.clock_rate;
3572 conf_port->info.fmt.det.aud.channel_count = param->base.channel_count;
Benny Prijonoc45d9512010-12-10 11:04:30 +00003573 conf_port->info.fmt.det.aud.bits_per_sample = 16;
Benny Prijonof798e502009-03-09 13:08:16 +00003574 }
3575
Benny Prijonoc45d9512010-12-10 11:04:30 +00003576
Benny Prijonof798e502009-03-09 13:08:16 +00003577 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00003578 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
3579 conf_port );
3580 if (status != PJ_SUCCESS) {
3581 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
3582 "sound device", status);
3583 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3584 pjsua_var.snd_port = NULL;
Benny Prijonob90fd382011-09-18 14:59:56 +00003585 goto on_error;
Benny Prijono52a93912006-08-04 20:54:37 +00003586 }
3587
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003588 /* Save the device IDs */
Sauw Ming98766c72011-03-11 06:57:24 +00003589 pjsua_var.cap_dev = param->base.rec_id;
3590 pjsua_var.play_dev = param->base.play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003591
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003592 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00003593 {
3594 pjmedia_aud_dev_info rec_info;
3595 pjmedia_aud_stream *strm;
3596 pjmedia_aud_param si;
3597 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003598
Benny Prijonof798e502009-03-09 13:08:16 +00003599 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3600 status = pjmedia_aud_stream_get_param(strm, &si);
3601 if (status == PJ_SUCCESS)
3602 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00003603
Benny Prijonof798e502009-03-09 13:08:16 +00003604 if (status==PJ_SUCCESS) {
Sauw Ming98766c72011-03-11 06:57:24 +00003605 if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) {
Benny Prijonof798e502009-03-09 13:08:16 +00003606 char tmp_buf[128];
3607 int tmp_buf_len = sizeof(tmp_buf);
3608
3609 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
3610 "%s (%dKHz)",
3611 rec_info.name,
Sauw Ming98766c72011-03-11 06:57:24 +00003612 param->base.clock_rate/1000);
Benny Prijonof798e502009-03-09 13:08:16 +00003613 pj_strset(&tmp, tmp_buf, tmp_buf_len);
3614 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
3615 } else {
3616 pjmedia_conf_set_port0_name(pjsua_var.mconf,
3617 pj_cstr(&tmp, rec_info.name));
3618 }
3619 }
3620
3621 /* Any error is not major, let it through */
3622 status = PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003623 }
Benny Prijonof798e502009-03-09 13:08:16 +00003624
3625 /* If this is the first time the audio device is open, retrieve some
3626 * settings from the device (such as volume settings) so that the
3627 * pjsua_snd_get_setting() work.
3628 */
3629 if (pjsua_var.aud_open_cnt == 0) {
3630 update_initial_aud_param();
3631 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00003632 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003633
Benny Prijonob90fd382011-09-18 14:59:56 +00003634 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003635 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003636
3637on_error:
3638 pj_log_pop_indent();
3639 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00003640}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003641
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003642
Benny Prijonof798e502009-03-09 13:08:16 +00003643/* Close existing sound device */
3644static void close_snd_dev(void)
3645{
Benny Prijonob90fd382011-09-18 14:59:56 +00003646 pj_log_push_indent();
3647
Benny Prijono2d647722011-07-13 03:05:22 +00003648 /* Notify app */
3649 if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3650 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0);
3651 }
3652
Benny Prijonof798e502009-03-09 13:08:16 +00003653 /* Close sound device */
3654 if (pjsua_var.snd_port) {
3655 pjmedia_aud_dev_info cap_info, play_info;
3656 pjmedia_aud_stream *strm;
3657 pjmedia_aud_param param;
3658
3659 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3660 pjmedia_aud_stream_get_param(strm, &param);
3661
3662 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
3663 cap_info.name[0] = '\0';
3664 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
3665 play_info.name[0] = '\0';
3666
3667 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
3668 "%s sound capture device",
3669 play_info.name, cap_info.name));
3670
3671 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
3672 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3673 pjsua_var.snd_port = NULL;
3674 }
3675
3676 /* Close null sound device */
3677 if (pjsua_var.null_snd) {
3678 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
3679 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
3680 pjsua_var.null_snd = NULL;
3681 }
3682
3683 if (pjsua_var.snd_pool)
3684 pj_pool_release(pjsua_var.snd_pool);
3685 pjsua_var.snd_pool = NULL;
Benny Prijono2d647722011-07-13 03:05:22 +00003686 pjsua_var.snd_is_on = PJ_FALSE;
Benny Prijonob90fd382011-09-18 14:59:56 +00003687
3688 pj_log_pop_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003689}
3690
3691
3692/*
3693 * Select or change sound device. Application may call this function at
3694 * any time to replace current sound device.
3695 */
3696PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
3697 int playback_dev)
3698{
3699 unsigned alt_cr_cnt = 1;
3700 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
3701 unsigned i;
3702 pj_status_t status = -1;
3703
Benny Prijonob90fd382011-09-18 14:59:56 +00003704 PJ_LOG(4,(THIS_FILE, "Set sound device: capture=%d, playback=%d",
3705 capture_dev, playback_dev));
3706 pj_log_push_indent();
3707
Benny Prijono23ea21a2009-06-03 12:43:06 +00003708 /* Null-sound */
3709 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003710 status = pjsua_set_null_snd_dev();
3711 pj_log_pop_indent();
3712 return status;
Benny Prijono23ea21a2009-06-03 12:43:06 +00003713 }
3714
Benny Prijonof798e502009-03-09 13:08:16 +00003715 /* Set default clock rate */
3716 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
3717 if (alt_cr[0] == 0)
3718 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
3719
3720 /* Allow retrying of different clock rate if we're using conference
3721 * bridge (meaning audio format is always PCM), otherwise lock on
3722 * to one clock rate.
3723 */
3724 if (pjsua_var.is_mswitch) {
3725 alt_cr_cnt = 1;
3726 } else {
3727 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
3728 }
3729
3730 /* Attempts to open the sound device with different clock rates */
3731 for (i=0; i<alt_cr_cnt; ++i) {
Sauw Ming98766c72011-03-11 06:57:24 +00003732 pjmedia_snd_port_param param;
Benny Prijonof798e502009-03-09 13:08:16 +00003733 unsigned samples_per_frame;
3734
3735 /* Create the default audio param */
3736 samples_per_frame = alt_cr[i] *
3737 pjsua_var.media_cfg.audio_frame_ptime *
3738 pjsua_var.media_cfg.channel_count / 1000;
Sauw Ming98766c72011-03-11 06:57:24 +00003739 status = create_aud_param(&param.base, capture_dev, playback_dev,
Benny Prijonof798e502009-03-09 13:08:16 +00003740 alt_cr[i], pjsua_var.media_cfg.channel_count,
3741 samples_per_frame, 16);
3742 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00003743 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003744
3745 /* Open! */
Sauw Ming98766c72011-03-11 06:57:24 +00003746 param.options = 0;
Benny Prijonof798e502009-03-09 13:08:16 +00003747 status = open_snd_dev(&param);
3748 if (status == PJ_SUCCESS)
3749 break;
3750 }
3751
3752 if (status != PJ_SUCCESS) {
3753 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003754 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003755 }
3756
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003757 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00003758 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003759
Benny Prijonob90fd382011-09-18 14:59:56 +00003760 pj_log_pop_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003761 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003762
3763on_error:
3764 pj_log_pop_indent();
3765 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003766}
3767
3768
3769/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00003770 * Get currently active sound devices. If sound devices has not been created
3771 * (for example when pjsua_start() is not called), it is possible that
3772 * the function returns PJ_SUCCESS with -1 as device IDs.
3773 */
3774PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
3775 int *playback_dev)
3776{
3777 if (capture_dev) {
3778 *capture_dev = pjsua_var.cap_dev;
3779 }
3780 if (playback_dev) {
3781 *playback_dev = pjsua_var.play_dev;
3782 }
3783
3784 return PJ_SUCCESS;
3785}
3786
3787
3788/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003789 * Use null sound device.
3790 */
3791PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
3792{
3793 pjmedia_port *conf_port;
3794 pj_status_t status;
3795
Benny Prijonob90fd382011-09-18 14:59:56 +00003796 PJ_LOG(4,(THIS_FILE, "Setting null sound device.."));
3797 pj_log_push_indent();
3798
3799
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003800 /* Close existing sound device */
3801 close_snd_dev();
3802
Benny Prijono2d647722011-07-13 03:05:22 +00003803 /* Notify app */
3804 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3805 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
3806 }
3807
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003808 /* Create memory pool for sound device. */
3809 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3810 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3811
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003812 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
3813
3814 /* Get the port0 of the conference bridge. */
3815 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3816 pj_assert(conf_port != NULL);
3817
3818 /* Create master port, connecting port0 of the conference bridge to
3819 * a null port.
3820 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003821 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003822 conf_port, 0, &pjsua_var.null_snd);
3823 if (status != PJ_SUCCESS) {
3824 pjsua_perror(THIS_FILE, "Unable to create null sound device",
3825 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003826 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003827 return status;
3828 }
3829
3830 /* Start the master port */
3831 status = pjmedia_master_port_start(pjsua_var.null_snd);
3832 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
3833
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003834 pjsua_var.cap_dev = NULL_SND_DEV_ID;
3835 pjsua_var.play_dev = NULL_SND_DEV_ID;
3836
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003837 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00003838 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003839
Benny Prijonob90fd382011-09-18 14:59:56 +00003840 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003841 return PJ_SUCCESS;
3842}
3843
3844
Benny Prijonoe909eac2006-07-27 22:04:56 +00003845
3846/*
3847 * Use no device!
3848 */
3849PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
3850{
3851 /* Close existing sound device */
3852 close_snd_dev();
3853
3854 pjsua_var.no_snd = PJ_TRUE;
3855 return pjmedia_conf_get_master_port(pjsua_var.mconf);
3856}
3857
3858
Benny Prijonof20687a2006-08-04 18:27:19 +00003859/*
3860 * Configure the AEC settings of the sound port.
3861 */
Benny Prijono5da50432006-08-07 10:24:52 +00003862PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00003863{
3864 pjsua_var.media_cfg.ec_tail_len = tail_ms;
3865
3866 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00003867 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
3868 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00003869
3870 return PJ_SUCCESS;
3871}
3872
3873
3874/*
3875 * Get current AEC tail length.
3876 */
Benny Prijono22dfe592006-08-06 12:07:13 +00003877PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00003878{
3879 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
3880 return PJ_SUCCESS;
3881}
3882
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00003883
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003884/*
Benny Prijonof798e502009-03-09 13:08:16 +00003885 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003886 */
Benny Prijonof798e502009-03-09 13:08:16 +00003887PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003888{
Benny Prijonof798e502009-03-09 13:08:16 +00003889 return pjsua_var.snd_port != NULL;
3890}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003891
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003892
Benny Prijonof798e502009-03-09 13:08:16 +00003893/*
3894 * Configure sound device setting to the sound device being used.
3895 */
3896PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
3897 const void *pval,
3898 pj_bool_t keep)
3899{
Benny Prijono09b0ff62009-03-10 12:07:51 +00003900 pj_status_t status;
3901
Benny Prijonof798e502009-03-09 13:08:16 +00003902 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003903 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00003904 return PJMEDIA_EAUD_INVCAP;
3905 }
3906
Benny Prijono09b0ff62009-03-10 12:07:51 +00003907 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00003908 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00003909 pjmedia_aud_stream *strm;
3910
3911 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003912 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00003913 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00003914 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00003915 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00003916
3917 if (status != PJ_SUCCESS)
3918 return status;
3919
3920 /* Save in internal param for later device open */
3921 if (keep) {
3922 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
3923 cap, pval);
3924 }
3925
3926 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00003927}
3928
3929/*
3930 * Retrieve a sound device setting.
3931 */
3932PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
3933 void *pval)
3934{
3935 /* If sound device has never been opened before, open it to
3936 * retrieve the initial setting from the device (e.g. audio
3937 * volume)
3938 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003939 if (pjsua_var.aud_open_cnt==0) {
3940 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00003941 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003942 close_snd_dev();
3943 }
Benny Prijonof798e502009-03-09 13:08:16 +00003944
3945 if (pjsua_snd_is_active()) {
3946 /* Sound is active, retrieve from device directly */
3947 pjmedia_aud_stream *strm;
3948
3949 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3950 return pjmedia_aud_stream_get_cap(strm, cap, pval);
3951 } else {
3952 /* Otherwise retrieve from internal param */
3953 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
3954 cap, pval);
3955 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003956}
3957
3958
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003959/*****************************************************************************
3960 * Codecs.
3961 */
3962
3963/*
3964 * Enum all supported codecs in the system.
3965 */
3966PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
3967 unsigned *p_count )
3968{
3969 pjmedia_codec_mgr *codec_mgr;
3970 pjmedia_codec_info info[32];
3971 unsigned i, count, prio[32];
3972 pj_status_t status;
3973
3974 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3975 count = PJ_ARRAY_SIZE(info);
3976 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
3977 if (status != PJ_SUCCESS) {
3978 *p_count = 0;
3979 return status;
3980 }
3981
3982 if (count > *p_count) count = *p_count;
3983
3984 for (i=0; i<count; ++i) {
Nanang Izzuddin56b2ce42011-04-06 13:55:01 +00003985 pj_bzero(&id[i], sizeof(pjsua_codec_info));
3986
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003987 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
3988 id[i].codec_id = pj_str(id[i].buf_);
3989 id[i].priority = (pj_uint8_t) prio[i];
3990 }
3991
3992 *p_count = count;
3993
3994 return PJ_SUCCESS;
3995}
3996
3997
3998/*
3999 * Change codec priority.
4000 */
4001PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
4002 pj_uint8_t priority )
4003{
Benny Prijono88accae2008-06-26 15:48:14 +00004004 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004005 pjmedia_codec_mgr *codec_mgr;
4006
4007 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4008
Benny Prijono88accae2008-06-26 15:48:14 +00004009 if (codec_id->slen==1 && *codec_id->ptr=='*')
4010 codec_id = &all;
4011
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004012 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
4013 priority);
4014}
4015
4016
4017/*
4018 * Get codec parameters.
4019 */
4020PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
4021 pjmedia_codec_param *param )
4022{
Benny Prijono88accae2008-06-26 15:48:14 +00004023 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004024 const pjmedia_codec_info *info;
4025 pjmedia_codec_mgr *codec_mgr;
4026 unsigned count = 1;
4027 pj_status_t status;
4028
4029 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4030
Benny Prijono88accae2008-06-26 15:48:14 +00004031 if (codec_id->slen==1 && *codec_id->ptr=='*')
4032 codec_id = &all;
4033
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004034 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
4035 &count, &info, NULL);
4036 if (status != PJ_SUCCESS)
4037 return status;
4038
4039 if (count != 1)
Nanang Izzuddin50fae732011-03-22 09:49:23 +00004040 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004041
4042 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
4043 return status;
4044}
4045
4046
4047/*
4048 * Set codec parameters.
4049 */
Nanang Izzuddin06839e72010-01-27 11:48:31 +00004050PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004051 const pjmedia_codec_param *param)
4052{
Nanang Izzuddin06839e72010-01-27 11:48:31 +00004053 const pjmedia_codec_info *info[2];
4054 pjmedia_codec_mgr *codec_mgr;
4055 unsigned count = 2;
4056 pj_status_t status;
4057
4058 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
4059
4060 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
4061 &count, info, NULL);
4062 if (status != PJ_SUCCESS)
4063 return status;
4064
4065 /* Codec ID should be specific, except for G.722.1 */
4066 if (count > 1 &&
4067 pj_strnicmp2(codec_id, "G7221/16", 8) != 0 &&
4068 pj_strnicmp2(codec_id, "G7221/32", 8) != 0)
4069 {
4070 pj_assert(!"Codec ID is not specific");
4071 return PJ_ETOOMANY;
4072 }
4073
4074 status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param);
4075 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004076}
Nanang Izzuddin50fae732011-03-22 09:49:23 +00004077
4078