blob: ff5a0ef9ebf9b1e7475c34b85718a2b81c778808 [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
713 return PJ_SUCCESS;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000714
715on_error:
716 if (call_med->tp)
717 pjmedia_transport_close(call_med->tp);
718
719 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000720}
721
Benny Prijono0bc99a92011-03-17 04:34:43 +0000722#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +0000723/* Create normal UDP media transports */
724static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000725{
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000726 unsigned i;
727 pj_status_t status;
728
Benny Prijono0bc99a92011-03-17 04:34:43 +0000729 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
730 pjsua_call *call = &pjsua_var.calls[i];
731 unsigned strm_idx;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000732
Benny Prijono0bc99a92011-03-17 04:34:43 +0000733 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
734 pjsua_call_media *call_med = &call->media[strm_idx];
735
736 status = create_udp_media_transport(cfg, &call_med->tp);
737 if (status != PJ_SUCCESS)
738 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000739 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000740 }
741
Benny Prijonoc97608e2007-03-23 16:34:20 +0000742 return PJ_SUCCESS;
743
744on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000745 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
746 pjsua_call *call = &pjsua_var.calls[i];
747 unsigned strm_idx;
748
749 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
750 pjsua_call_media *call_med = &call->media[strm_idx];
751
752 if (call_med->tp) {
753 pjmedia_transport_close(call_med->tp);
754 call_med->tp = NULL;
755 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000756 }
757 }
Benny Prijonoc97608e2007-03-23 16:34:20 +0000758 return status;
759}
Benny Prijono0bc99a92011-03-17 04:34:43 +0000760#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +0000761
Benny Prijono096c56c2007-09-15 08:30:16 +0000762/* This callback is called when ICE negotiation completes */
Benny Prijonof76e1392008-06-06 14:51:48 +0000763static void on_ice_complete(pjmedia_transport *tp,
764 pj_ice_strans_op op,
765 pj_status_t result)
Benny Prijono096c56c2007-09-15 08:30:16 +0000766{
Benny Prijono0bc99a92011-03-17 04:34:43 +0000767 pjsua_call_media *call_med = (pjsua_call_media*)tp->user_data;
Benny Prijono096c56c2007-09-15 08:30:16 +0000768
Benny Prijono0bc99a92011-03-17 04:34:43 +0000769 if (!call_med)
Benny Prijonof76e1392008-06-06 14:51:48 +0000770 return;
771
772 switch (op) {
773 case PJ_ICE_STRANS_OP_INIT:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000774 call_med->tp_ready = result;
Benny Prijonof76e1392008-06-06 14:51:48 +0000775 break;
776 case PJ_ICE_STRANS_OP_NEGOTIATION:
777 if (result != PJ_SUCCESS) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000778 call_med->state = PJSUA_CALL_MEDIA_ERROR;
779 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijonof76e1392008-06-06 14:51:48 +0000780
Benny Prijono0bc99a92011-03-17 04:34:43 +0000781 if (call_med->call && pjsua_var.ua_cfg.cb.on_call_media_state) {
782 pjsua_var.ua_cfg.cb.on_call_media_state(call_med->call->index);
Benny Prijonof76e1392008-06-06 14:51:48 +0000783 }
Benny Prijono0bc99a92011-03-17 04:34:43 +0000784 } else if (call_med->call) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000785 /* Send UPDATE if default transport address is different than
786 * what was advertised (ticket #881)
787 */
788 pjmedia_transport_info tpinfo;
789 pjmedia_ice_transport_info *ii = NULL;
790 unsigned i;
791
792 pjmedia_transport_info_init(&tpinfo);
793 pjmedia_transport_get_info(tp, &tpinfo);
794 for (i=0; i<tpinfo.specific_info_cnt; ++i) {
795 if (tpinfo.spc_info[i].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
796 ii = (pjmedia_ice_transport_info*)
797 tpinfo.spc_info[i].buffer;
798 break;
799 }
800 }
801
802 if (ii && ii->role==PJ_ICE_SESS_ROLE_CONTROLLING &&
803 pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000804 &call_med->rtp_addr))
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000805 {
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000806 pj_bool_t use_update;
807 const pj_str_t STR_UPDATE = { "UPDATE", 6 };
808 pjsip_dialog_cap_status support_update;
809 pjsip_dialog *dlg;
810
Benny Prijono0bc99a92011-03-17 04:34:43 +0000811 dlg = call_med->call->inv->dlg;
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000812 support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW,
813 NULL, &STR_UPDATE);
814 use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED);
815
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000816 PJ_LOG(4,(THIS_FILE,
817 "ICE default transport address has changed for "
Benny Prijono0bc99a92011-03-17 04:34:43 +0000818 "call %d, sending %s", call_med->call->index,
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000819 (use_update ? "UPDATE" : "re-INVITE")));
820
821 if (use_update)
Benny Prijono0bc99a92011-03-17 04:34:43 +0000822 pjsua_call_update(call_med->call->index, 0, NULL);
Benny Prijonoa8f9e622010-06-21 13:28:55 +0000823 else
Benny Prijono0bc99a92011-03-17 04:34:43 +0000824 pjsua_call_reinvite(call_med->call->index, 0, NULL);
Benny Prijonof5d9f1f2009-10-14 13:13:18 +0000825 }
Benny Prijonof76e1392008-06-06 14:51:48 +0000826 }
827 break;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000828 case PJ_ICE_STRANS_OP_KEEP_ALIVE:
829 if (result != PJ_SUCCESS) {
830 PJ_PERROR(4,(THIS_FILE, result,
Benny Prijono0bc99a92011-03-17 04:34:43 +0000831 "ICE keep alive failure for transport %d:%d",
832 call_med->call->index, call_med->idx));
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000833 }
834 if (pjsua_var.ua_cfg.cb.on_ice_transport_error) {
Benny Prijono0bc99a92011-03-17 04:34:43 +0000835 pjsua_call_id id = call_med->call->index;
Benny Prijono4d6ff4d2010-06-19 12:35:33 +0000836 (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result,
837 NULL);
838 }
839 break;
Benny Prijono096c56c2007-09-15 08:30:16 +0000840 }
841}
842
843
Benny Prijonof76e1392008-06-06 14:51:48 +0000844/* Parse "HOST:PORT" format */
845static pj_status_t parse_host_port(const pj_str_t *host_port,
846 pj_str_t *host, pj_uint16_t *port)
Benny Prijonoc97608e2007-03-23 16:34:20 +0000847{
Benny Prijonof76e1392008-06-06 14:51:48 +0000848 pj_str_t str_port;
849
850 str_port.ptr = pj_strchr(host_port, ':');
851 if (str_port.ptr != NULL) {
852 int iport;
853
854 host->ptr = host_port->ptr;
855 host->slen = (str_port.ptr - host->ptr);
856 str_port.ptr++;
857 str_port.slen = host_port->slen - host->slen - 1;
858 iport = (int)pj_strtoul(&str_port);
859 if (iport < 1 || iport > 65535)
860 return PJ_EINVAL;
861 *port = (pj_uint16_t)iport;
862 } else {
863 *host = *host_port;
864 *port = 0;
865 }
866
867 return PJ_SUCCESS;
868}
869
870/* Create ICE media transports (when ice is enabled) */
Benny Prijono0bc99a92011-03-17 04:34:43 +0000871static pj_status_t create_ice_media_transport(
872 const pjsua_transport_config *cfg,
873 pjsua_call_media *call_med)
Benny Prijonof76e1392008-06-06 14:51:48 +0000874{
875 char stunip[PJ_INET6_ADDRSTRLEN];
876 pj_ice_strans_cfg ice_cfg;
Benny Prijono0bc99a92011-03-17 04:34:43 +0000877 pjmedia_ice_cb ice_cb;
878 char name[32];
879 unsigned comp_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000880 pj_status_t status;
881
Benny Prijonoda9785b2007-04-02 20:43:06 +0000882 /* Make sure STUN server resolution has completed */
Benny Prijonobb995fd2009-08-12 11:03:23 +0000883 status = resolve_stun_server(PJ_TRUE);
Benny Prijonoda9785b2007-04-02 20:43:06 +0000884 if (status != PJ_SUCCESS) {
885 pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
886 return status;
887 }
888
Benny Prijonof76e1392008-06-06 14:51:48 +0000889 /* Create ICE stream transport configuration */
890 pj_ice_strans_cfg_default(&ice_cfg);
891 pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
892 pjsip_endpt_get_ioqueue(pjsua_var.endpt),
893 pjsip_endpt_get_timer_heap(pjsua_var.endpt));
894
895 ice_cfg.af = pj_AF_INET();
896 ice_cfg.resolver = pjsua_var.resolver;
897
Benny Prijono329d6382009-05-29 13:04:03 +0000898 ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
899
Benny Prijonof76e1392008-06-06 14:51:48 +0000900 /* Configure STUN settings */
901 if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
902 pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
903 ice_cfg.stun.server = pj_str(stunip);
904 ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
905 }
Benny Prijono329d6382009-05-29 13:04:03 +0000906 if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
907 ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
Benny Prijonof76e1392008-06-06 14:51:48 +0000908
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000909 /* Copy QoS setting to STUN setting */
910 ice_cfg.stun.cfg.qos_type = cfg->qos_type;
911 pj_memcpy(&ice_cfg.stun.cfg.qos_params, &cfg->qos_params,
912 sizeof(cfg->qos_params));
913
Benny Prijonof76e1392008-06-06 14:51:48 +0000914 /* Configure TURN settings */
915 if (pjsua_var.media_cfg.enable_turn) {
916 status = parse_host_port(&pjsua_var.media_cfg.turn_server,
917 &ice_cfg.turn.server,
918 &ice_cfg.turn.port);
919 if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
920 PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
921 return PJ_EINVAL;
922 }
923 if (ice_cfg.turn.port == 0)
924 ice_cfg.turn.port = 3479;
925 ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
926 pj_memcpy(&ice_cfg.turn.auth_cred,
927 &pjsua_var.media_cfg.turn_auth_cred,
928 sizeof(ice_cfg.turn.auth_cred));
Benny Prijono4d79b0f2009-10-25 09:02:07 +0000929
930 /* Copy QoS setting to TURN setting */
931 ice_cfg.turn.cfg.qos_type = cfg->qos_type;
932 pj_memcpy(&ice_cfg.turn.cfg.qos_params, &cfg->qos_params,
933 sizeof(cfg->qos_params));
Benny Prijonof76e1392008-06-06 14:51:48 +0000934 }
Benny Prijonob681a2f2007-03-25 18:44:51 +0000935
Benny Prijono0bc99a92011-03-17 04:34:43 +0000936 pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
937 ice_cb.on_ice_complete = &on_ice_complete;
938 pj_ansi_snprintf(name, sizeof(name), "icetp%02d", call_med->idx);
939 call_med->tp_ready = PJ_EPENDING;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000940
Benny Prijono0bc99a92011-03-17 04:34:43 +0000941 comp_cnt = 1;
942 if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
943 ++comp_cnt;
Benny Prijonof76e1392008-06-06 14:51:48 +0000944
Benny Prijonobd6613f2011-04-11 17:27:14 +0000945 status = pjmedia_ice_create3(pjsua_var.med_endpt, name, comp_cnt,
946 &ice_cfg, &ice_cb, 0, call_med,
947 &call_med->tp);
Benny Prijono0bc99a92011-03-17 04:34:43 +0000948 if (status != PJ_SUCCESS) {
949 pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
950 status);
951 goto on_error;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000952 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000953
Benny Prijono0bc99a92011-03-17 04:34:43 +0000954 /* Wait until transport is initialized, or time out */
955 PJSUA_UNLOCK();
956 while (call_med->tp_ready == PJ_EPENDING) {
957 pjsua_handle_events(100);
958 }
959 PJSUA_LOCK();
960 if (call_med->tp_ready != PJ_SUCCESS) {
961 pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
962 call_med->tp_ready);
963 status = call_med->tp_ready;
964 goto on_error;
965 }
966
967 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
968 pjsua_var.media_cfg.tx_drop_pct);
969
970 pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
971 pjsua_var.media_cfg.rx_drop_pct);
972
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000973 return PJ_SUCCESS;
974
975on_error:
Benny Prijono0bc99a92011-03-17 04:34:43 +0000976 if (call_med->tp != NULL) {
977 pjmedia_transport_close(call_med->tp);
978 call_med->tp = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000979 }
980
Benny Prijonoc97608e2007-03-23 16:34:20 +0000981 return status;
982}
983
Benny Prijono0bc99a92011-03-17 04:34:43 +0000984#if DISABLED_FOR_TICKET_1185
985/* Create ICE media transports (when ice is enabled) */
986static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
987{
988 unsigned i;
989 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +0000990
Benny Prijono0bc99a92011-03-17 04:34:43 +0000991 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
992 pjsua_call *call = &pjsua_var.calls[i];
993 unsigned strm_idx;
994
995 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
996 pjsua_call_media *call_med = &call->media[strm_idx];
997
998 status = create_ice_media_transport(cfg, call_med);
999 if (status != PJ_SUCCESS)
1000 goto on_error;
1001 }
1002 }
1003
1004 return PJ_SUCCESS;
1005
1006on_error:
1007 for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
1008 pjsua_call *call = &pjsua_var.calls[i];
1009 unsigned strm_idx;
1010
1011 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1012 pjsua_call_media *call_med = &call->media[strm_idx];
1013
1014 if (call_med->tp) {
1015 pjmedia_transport_close(call_med->tp);
1016 call_med->tp = NULL;
1017 }
1018 }
1019 }
1020 return status;
1021}
1022#endif
1023
1024#if DISABLED_FOR_TICKET_1185
Benny Prijonoc97608e2007-03-23 16:34:20 +00001025/*
Benny Prijono0bc99a92011-03-17 04:34:43 +00001026 * Create media transports for all the calls. This function creates
Benny Prijonoc97608e2007-03-23 16:34:20 +00001027 * one UDP media transport for each call.
1028 */
Benny Prijono1f61a8f2007-08-16 10:11:44 +00001029PJ_DEF(pj_status_t) pjsua_media_transports_create(
1030 const pjsua_transport_config *app_cfg)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001031{
1032 pjsua_transport_config cfg;
1033 unsigned i;
1034 pj_status_t status;
1035
1036
1037 /* Make sure pjsua_init() has been called */
1038 PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
1039
1040 PJSUA_LOCK();
1041
1042 /* Delete existing media transports */
1043 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001044 pjsua_call *call = &pjsua_var.calls[i];
1045 unsigned strm_idx;
1046
1047 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1048 pjsua_call_media *call_med = &call->media[strm_idx];
1049
1050 if (call_med->tp && call_med->tp_auto_del) {
1051 pjmedia_transport_close(call_med->tp);
1052 call_med->tp = NULL;
1053 call_med->tp_orig = NULL;
1054 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001055 }
1056 }
1057
1058 /* Copy config */
1059 pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
1060
Benny Prijono40860c32008-09-04 13:55:33 +00001061 /* Create the transports */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001062 if (pjsua_var.media_cfg.enable_ice) {
Benny Prijono4d79b0f2009-10-25 09:02:07 +00001063 status = create_ice_media_transports(&cfg);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001064 } else {
1065 status = create_udp_media_transports(&cfg);
1066 }
1067
Benny Prijono40860c32008-09-04 13:55:33 +00001068 /* Set media transport auto_delete to True */
1069 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001070 pjsua_call *call = &pjsua_var.calls[i];
1071 unsigned strm_idx;
1072
1073 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1074 pjsua_call_media *call_med = &call->media[strm_idx];
1075
1076 call_med->tp_auto_del = PJ_TRUE;
1077 }
Benny Prijono40860c32008-09-04 13:55:33 +00001078 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001079
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001080 PJSUA_UNLOCK();
1081
1082 return status;
1083}
1084
Benny Prijono40860c32008-09-04 13:55:33 +00001085/*
1086 * Attach application's created media transports.
1087 */
1088PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
1089 unsigned count,
1090 pj_bool_t auto_delete)
1091{
1092 unsigned i;
1093
1094 PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
1095
1096 /* Assign the media transports */
1097 for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001098 pjsua_call *call = &pjsua_var.calls[i];
1099 unsigned strm_idx;
1100
1101 for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
1102 pjsua_call_media *call_med = &call->media[strm_idx];
1103
1104 if (call_med->tp && call_med->tp_auto_del) {
1105 pjmedia_transport_close(call_med->tp);
1106 call_med->tp = NULL;
1107 call_med->tp_orig = NULL;
1108 }
Benny Prijono40860c32008-09-04 13:55:33 +00001109 }
1110
Benny Prijono0bc99a92011-03-17 04:34:43 +00001111 PJ_TODO(remove_pjsua_media_transports_attach);
1112
1113 call->media[0].tp = tp[i].transport;
1114 call->media[0].tp_auto_del = auto_delete;
Benny Prijono40860c32008-09-04 13:55:33 +00001115 }
1116
1117 return PJ_SUCCESS;
1118}
Benny Prijono0bc99a92011-03-17 04:34:43 +00001119#endif
Benny Prijono40860c32008-09-04 13:55:33 +00001120
Benny Prijono0bc99a92011-03-17 04:34:43 +00001121/* Go through the list of media in the SDP, find acceptable media, and
1122 * sort them based on the "quality" of the media, and store the indexes
1123 * in the specified array. Media with the best quality will be listed
1124 * first in the array. The quality factors considered currently is
1125 * encryption.
1126 */
1127static void sort_media(const pjmedia_sdp_session *sdp,
1128 const pj_str_t *type,
1129 pjmedia_srtp_use use_srtp,
1130 pj_uint8_t midx[],
1131 unsigned *p_count)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001132{
1133 unsigned i;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001134 unsigned count = 0;
1135 int score[PJSUA_MAX_CALL_MEDIA];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001136
Benny Prijono0bc99a92011-03-17 04:34:43 +00001137 pj_assert(*p_count >= PJSUA_MAX_CALL_MEDIA);
1138
1139 *p_count = 0;
Benny Prijono09c0d672011-04-11 05:03:24 +00001140 for (i=0; i<PJSUA_MAX_CALL_MEDIA; ++i)
1141 score[i] = 1;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001142
1143 /* Score each media */
1144 for (i=0; i<sdp->media_count && count<PJSUA_MAX_CALL_MEDIA; ++i) {
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001145 const pjmedia_sdp_media *m = sdp->media[i];
Nanang Izzuddina6414292011-04-08 04:26:18 +00001146 const pjmedia_sdp_conn *c;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001147
Benny Prijono0bc99a92011-03-17 04:34:43 +00001148 /* Skip different media */
1149 if (pj_stricmp(&m->desc.media, type) != 0) {
1150 score[count++] = -22000;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001151 continue;
1152 }
1153
Nanang Izzuddina6414292011-04-08 04:26:18 +00001154 c = m->conn? m->conn : sdp->conn;
1155
Benny Prijono0bc99a92011-03-17 04:34:43 +00001156 /* Supported transports */
1157 if (pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0) {
1158 switch (use_srtp) {
1159 case PJMEDIA_SRTP_MANDATORY:
1160 case PJMEDIA_SRTP_OPTIONAL:
1161 ++score[i];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001162 break;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001163 case PJMEDIA_SRTP_DISABLED:
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001164 //--score[i];
1165 score[i] -= 5;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001166 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001167 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001168 } else if (pj_stricmp2(&m->desc.transport, "RTP/AVP")==0) {
1169 switch (use_srtp) {
1170 case PJMEDIA_SRTP_MANDATORY:
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001171 //--score[i];
1172 score[i] -= 5;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001173 break;
1174 case PJMEDIA_SRTP_OPTIONAL:
1175 /* No change in score */
1176 break;
1177 case PJMEDIA_SRTP_DISABLED:
1178 ++score[i];
1179 break;
1180 }
1181 } else {
1182 score[i] -= 10;
1183 }
1184
1185 /* Is media disabled? */
1186 if (m->desc.port == 0)
1187 score[i] -= 10;
1188
1189 /* Is media inactive? */
Nanang Izzuddina6414292011-04-08 04:26:18 +00001190 if (pjmedia_sdp_media_find_attr2(m, "inactive", NULL) ||
1191 pj_strcmp2(&c->addr, "0.0.0.0") == 0)
1192 {
1193 //score[i] -= 10;
1194 score[i] -= 1;
1195 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001196
1197 ++count;
1198 }
1199
1200 /* Created sorted list based on quality */
1201 for (i=0; i<count; ++i) {
1202 unsigned j;
1203 int best = 0;
1204
1205 for (j=1; j<count; ++j) {
1206 if (score[j] > score[best])
1207 best = j;
1208 }
1209 /* Don't put media with negative score, that media is unacceptable
1210 * for us.
1211 */
1212 if (score[best] >= 0) {
1213 midx[*p_count] = (pj_uint8_t)best;
1214 (*p_count)++;
1215 }
1216
1217 score[best] = -22000;
1218
1219 }
1220}
1221
Benny Prijonoee0ba182011-07-15 06:18:29 +00001222/* Callback to receive media events */
1223static pj_status_t call_media_on_event(pjmedia_event_subscription *esub,
1224 pjmedia_event *event)
1225{
1226 pjsua_call_media *call_med = (pjsua_call_media*)esub->user_data;
1227 pjsua_call *call = call_med->call;
1228
1229 if (pjsua_var.ua_cfg.cb.on_call_media_event && call) {
1230 ++event->proc_cnt;
1231 (*pjsua_var.ua_cfg.cb.on_call_media_event)(call->index,
1232 call_med->idx, event);
Benny Prijono53a7c702008-04-14 02:57:29 +00001233 }
1234
1235 return PJ_SUCCESS;
1236}
1237
Benny Prijono0bc99a92011-03-17 04:34:43 +00001238/* Initialize the media line */
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001239pj_status_t pjsua_call_media_init(pjsua_call_media *call_med,
1240 pjmedia_type type,
1241 const pjsua_transport_config *tcfg,
1242 int security_level,
1243 int *sip_err_code)
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001244{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001245 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
1246 pj_status_t status;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001247
Benny Prijono0bc99a92011-03-17 04:34:43 +00001248 /*
1249 * Note: this function may be called when the media already exists
1250 * (e.g. in reinvites, updates, etc.)
1251 */
1252 call_med->type = type;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001253
Benny Prijono0bc99a92011-03-17 04:34:43 +00001254 /* Create the media transport for initial call. This is blocking for now */
1255 if (call_med->tp == NULL) {
1256 if (pjsua_var.media_cfg.enable_ice) {
1257 status = create_ice_media_transport(tcfg, call_med);
1258 } else {
1259 status = create_udp_media_transport(tcfg, call_med);
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001260 }
1261
Benny Prijono0bc99a92011-03-17 04:34:43 +00001262 if (status != PJ_SUCCESS) {
1263 PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport"));
1264 return status;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001265 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001266
1267 call_med->tp_st = PJSUA_MED_TP_IDLE;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001268
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00001269#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001270 /* While in initial call, set default video devices */
1271 if (type == PJMEDIA_TYPE_VIDEO) {
1272 call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev;
1273 call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev;
1274 if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) {
1275 pjmedia_vid_dev_info info;
1276 pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info);
1277 call_med->strm.v.rdr_dev = info.id;
1278 }
1279 if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
1280 pjmedia_vid_dev_info info;
1281 pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info);
1282 call_med->strm.v.cap_dev = info.id;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001283 }
1284 }
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00001285#endif
1286
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001287 } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) {
1288 /* Media is being reenabled. */
1289 call_med->tp_st = PJSUA_MED_TP_INIT;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001290 }
1291
Benny Prijono0bc99a92011-03-17 04:34:43 +00001292#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1293 /* This function may be called when SRTP transport already exists
1294 * (e.g: in re-invite, update), don't need to destroy/re-create.
1295 */
1296 if (!call_med->tp_orig || call_med->tp == call_med->tp_orig) {
1297 pjmedia_srtp_setting srtp_opt;
1298 pjmedia_transport *srtp = NULL;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001299
Benny Prijono0bc99a92011-03-17 04:34:43 +00001300 /* Check if SRTP requires secure signaling */
1301 if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
1302 if (security_level < acc->cfg.srtp_secure_signaling) {
1303 if (sip_err_code)
1304 *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1305 status = PJSIP_ESESSIONINSECURE;
1306 goto on_error;
1307 }
1308 }
1309
1310 /* Always create SRTP adapter */
1311 pjmedia_srtp_setting_default(&srtp_opt);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001312 srtp_opt.close_member_tp = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001313 /* If media session has been ever established, let's use remote's
1314 * preference in SRTP usage policy, especially when it is stricter.
1315 */
1316 if (call_med->rem_srtp_use > acc->cfg.use_srtp)
1317 srtp_opt.use = call_med->rem_srtp_use;
1318 else
1319 srtp_opt.use = acc->cfg.use_srtp;
1320
1321 status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
1322 call_med->tp,
1323 &srtp_opt, &srtp);
1324 if (status != PJ_SUCCESS) {
1325 if (sip_err_code)
1326 *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
1327 goto on_error;
1328 }
1329
1330 /* Set SRTP as current media transport */
1331 call_med->tp_orig = call_med->tp;
1332 call_med->tp = srtp;
1333 }
1334#else
Benny Prijono7df19342011-07-23 02:54:03 +00001335 call_med->tp_orig = call_med->tp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001336 PJ_UNUSED_ARG(security_level);
1337#endif
1338
Benny Prijono1fe04ee2011-07-15 07:27:05 +00001339 pjmedia_event_subscription_init(&call_med->esub_rend, &call_media_on_event,
Benny Prijonoee0ba182011-07-15 06:18:29 +00001340 call_med);
Benny Prijono1fe04ee2011-07-15 07:27:05 +00001341 pjmedia_event_subscription_init(&call_med->esub_cap, &call_media_on_event,
1342 call_med);
Benny Prijonoee0ba182011-07-15 06:18:29 +00001343
Benny Prijono0bc99a92011-03-17 04:34:43 +00001344 return PJ_SUCCESS;
1345
1346on_error:
1347 if (call_med->tp) {
1348 pjmedia_transport_close(call_med->tp);
1349 call_med->tp = NULL;
1350 }
1351 return status;
1352}
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001353
Benny Prijonod8179652008-01-23 20:39:07 +00001354pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
1355 pjsip_role_e role,
1356 int security_level,
1357 pj_pool_t *tmp_pool,
1358 const pjmedia_sdp_session *rem_sdp,
1359 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001360{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001361 const pj_str_t STR_AUDIO = { "audio", 5 };
1362 const pj_str_t STR_VIDEO = { "video", 5 };
Benny Prijonod8179652008-01-23 20:39:07 +00001363 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijonod8179652008-01-23 20:39:07 +00001364 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001365 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1366 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
1367 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
1368 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
1369 pjmedia_type media_types[PJSUA_MAX_CALL_MEDIA];
1370 unsigned mi;
1371 pj_status_t status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001372
Benny Prijonod8179652008-01-23 20:39:07 +00001373 PJ_UNUSED_ARG(role);
1374
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001375 /*
1376 * Note: this function may be called when the media already exists
1377 * (e.g. in reinvites, updates, etc).
1378 */
1379
Benny Prijono0bc99a92011-03-17 04:34:43 +00001380 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
1381 return PJ_EBUSY;
1382
Benny Prijonob90fd382011-09-18 14:59:56 +00001383 PJ_LOG(4,(THIS_FILE, "Call %d: initializing media..", call_id));
1384 pj_log_push_indent();
1385
Benny Prijono0bc99a92011-03-17 04:34:43 +00001386#if DISABLED_FOR_TICKET_1185
Benny Prijonod8179652008-01-23 20:39:07 +00001387 /* Return error if media transport has not been created yet
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001388 * (e.g. application is starting)
1389 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001390 for (i=0; i<call->med_cnt; ++i) {
1391 if (call->media[i].tp == NULL) {
Benny Prijonob90fd382011-09-18 14:59:56 +00001392 status = PJ_EBUSY;
1393 goto on_error;
Benny Prijonod8179652008-01-23 20:39:07 +00001394 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001395 }
Benny Prijonod8179652008-01-23 20:39:07 +00001396#endif
Benny Prijonoc97608e2007-03-23 16:34:20 +00001397
Benny Prijono0bc99a92011-03-17 04:34:43 +00001398 if (rem_sdp) {
1399 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
1400 maudidx, &maudcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001401 // Don't apply media count limitation until SDP negotiation is done.
1402 //if (maudcnt > acc->cfg.max_audio_cnt)
1403 // maudcnt = acc->cfg.max_audio_cnt;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001404
Benny Prijono0bc99a92011-03-17 04:34:43 +00001405 if (maudcnt==0) {
1406 /* Expecting audio in the offer */
1407 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1408 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001409 status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
1410 goto on_error;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001411 }
1412
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001413#if PJMEDIA_HAS_VIDEO
Benny Prijono0bc99a92011-03-17 04:34:43 +00001414 sort_media(rem_sdp, &STR_VIDEO, acc->cfg.use_srtp,
1415 mvididx, &mvidcnt);
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001416 // Don't apply media count limitation until SDP negotiation is done.
1417 //if (mvidcnt > acc->cfg.max_video_cnt)
1418 //mvidcnt = acc->cfg.max_video_cnt;
1419#else
1420 mvidcnt = 0;
1421#endif
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001422
1423 /* Update media count only when remote add any media, this media count
1424 * must never decrease.
Benny Prijonod8179652008-01-23 20:39:07 +00001425 */
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001426 if (call->med_cnt < rem_sdp->media_count)
1427 call->med_cnt = PJ_MIN(rem_sdp->media_count, PJSUA_MAX_CALL_MEDIA);
Benny Prijonod8179652008-01-23 20:39:07 +00001428
Benny Prijono0bc99a92011-03-17 04:34:43 +00001429 } else {
1430 maudcnt = acc->cfg.max_audio_cnt;
1431 for (mi=0; mi<maudcnt; ++mi) {
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001432 maudidx[mi] = (pj_uint8_t)mi;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001433 media_types[mi] = PJMEDIA_TYPE_AUDIO;
Benny Prijonod8179652008-01-23 20:39:07 +00001434 }
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001435#if PJMEDIA_HAS_VIDEO
Benny Prijono0bc99a92011-03-17 04:34:43 +00001436 mvidcnt = acc->cfg.max_video_cnt;
1437 for (mi=0; mi<mvidcnt; ++mi) {
1438 media_types[maudcnt + mi] = PJMEDIA_TYPE_VIDEO;
1439 }
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001440#else
1441 mvidcnt = 0;
1442#endif
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001443 call->med_cnt = maudcnt + mvidcnt;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001444 }
1445
Benny Prijono0bc99a92011-03-17 04:34:43 +00001446 if (call->med_cnt == 0) {
1447 /* Expecting at least one media */
Benny Prijonoab8dba92008-06-27 21:59:15 +00001448 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001449 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001450 status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
1451 goto on_error;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001452 }
1453
Benny Prijono0bc99a92011-03-17 04:34:43 +00001454 /* Initialize each media line */
1455 for (mi=0; mi < call->med_cnt; ++mi) {
1456 pjsua_call_media *call_med = &call->media[mi];
1457 pj_bool_t enabled = PJ_FALSE;
1458 pjmedia_type media_type = PJMEDIA_TYPE_NONE;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001459
Benny Prijono0bc99a92011-03-17 04:34:43 +00001460 if (rem_sdp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001461 if (mi >= rem_sdp->media_count) {
1462 /* Media has been removed in remote re-offer */
1463 media_type = call_med->type;
1464 } else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_AUDIO)) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001465 media_type = PJMEDIA_TYPE_AUDIO;
1466 if (pj_memchr(maudidx, mi, maudcnt * sizeof(maudidx[0]))) {
1467 enabled = PJ_TRUE;
1468 }
1469 }
1470 else if (!pj_stricmp(&rem_sdp->media[mi]->desc.media, &STR_VIDEO)) {
1471 media_type = PJMEDIA_TYPE_VIDEO;
1472 if (pj_memchr(mvididx, mi, mvidcnt * sizeof(mvididx[0]))) {
1473 enabled = PJ_TRUE;
1474 }
1475 }
1476
1477 } else {
1478 enabled = PJ_TRUE;
1479 media_type = media_types[mi];
1480 }
1481
1482 if (enabled) {
1483 status = pjsua_call_media_init(call_med, media_type,
1484 &acc->cfg.rtp_cfg,
1485 security_level, sip_err_code);
1486 if (status != PJ_SUCCESS) {
1487 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001488 goto on_error;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001489 }
1490 } else {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001491 /* By convention, the media is disabled if transport is NULL
1492 * or transport state is PJSUA_MED_TP_DISABLED.
1493 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001494 if (call_med->tp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001495 // Don't close transport here, as SDP negotiation has not been
1496 // done and stream may be still active.
1497 //pjmedia_transport_close(call_med->tp);
1498 //call_med->tp = NULL;
1499 pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT ||
1500 call_med->tp_st == PJSUA_MED_TP_RUNNING);
1501 call_med->tp_st = PJSUA_MED_TP_DISABLED;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001502 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001503
1504 /* Put media type just for info */
1505 call_med->type = media_type;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001506 }
Benny Prijono224b4e22008-06-19 14:10:28 +00001507 }
1508
Benny Prijono0bc99a92011-03-17 04:34:43 +00001509 call->audio_idx = maudidx[0];
1510
1511 PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d",
1512 call->audio_idx, call->index));
1513
1514 /* Tell the media transport of a new offer/answer session */
1515 for (mi=0; mi < call->med_cnt; ++mi) {
1516 pjsua_call_media *call_med = &call->media[mi];
1517
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001518 /* Note: tp may be NULL if this media line is disabled */
1519 if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001520 status = pjmedia_transport_media_create(call_med->tp,
1521 tmp_pool, 0,
1522 rem_sdp, mi);
1523 if (status != PJ_SUCCESS) {
1524 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1525 pjsua_media_channel_deinit(call_id);
Benny Prijonob90fd382011-09-18 14:59:56 +00001526 goto on_error;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001527 }
1528
1529 call_med->tp_st = PJSUA_MED_TP_INIT;
1530 }
1531 }
1532
Benny Prijonob90fd382011-09-18 14:59:56 +00001533 pj_log_pop_indent();
Benny Prijonoc97608e2007-03-23 16:34:20 +00001534 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00001535
1536on_error:
1537 pj_log_pop_indent();
1538 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001539}
1540
1541pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
1542 pj_pool_t *pool,
Benny Prijonod8179652008-01-23 20:39:07 +00001543 const pjmedia_sdp_session *rem_sdp,
Benny Prijono25b2ea12008-01-24 19:20:54 +00001544 pjmedia_sdp_session **p_sdp,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001545 int *sip_err_code)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001546{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001547 enum { MAX_MEDIA = PJSUA_MAX_CALL_MEDIA };
Benny Prijonoc97608e2007-03-23 16:34:20 +00001548 pjmedia_sdp_session *sdp;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001549 pj_sockaddr origin;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001550 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001551 pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001552 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001553 pj_status_t status;
1554
Benny Prijono0bc99a92011-03-17 04:34:43 +00001555 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
Benny Prijono55e82352007-05-10 20:49:08 +00001556 return PJ_EBUSY;
Benny Prijono55e82352007-05-10 20:49:08 +00001557
Benny Prijono0bc99a92011-03-17 04:34:43 +00001558 if (rem_sdp) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001559 /* If this is a re-offer, let's re-initialize media as remote may
1560 * add or remove media
1561 */
1562 if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) {
1563 status = pjsua_media_channel_init(call_id, PJSIP_ROLE_UAS,
1564 call->secure_level, pool,
1565 rem_sdp, sip_err_code);
1566 if (status != PJ_SUCCESS)
1567 return status;
Benny Prijono0324ba52010-12-02 10:41:46 +00001568 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001569
1570#if 0
1571 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001572 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
1573 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001574
Benny Prijono0bc99a92011-03-17 04:34:43 +00001575 sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
1576 maudidx, &maudcnt);
Nanang Izzuddin3150d8b2010-12-01 08:20:28 +00001577
Benny Prijono0bc99a92011-03-17 04:34:43 +00001578 if (maudcnt==0) {
1579 /* Expecting audio in the offer */
1580 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
1581 pjsua_media_channel_deinit(call_id);
1582 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
Benny Prijono224b4e22008-06-19 14:10:28 +00001583 }
Benny Prijono224b4e22008-06-19 14:10:28 +00001584
Benny Prijono0bc99a92011-03-17 04:34:43 +00001585 call->audio_idx = maudidx[0];
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001586#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00001587 } else {
1588 /* Audio is first in our offer, by convention */
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001589 // The audio_idx should not be changed here, as this function may be
1590 // called in generating re-offer and the current active audio index
1591 // can be anywhere.
1592 //call->audio_idx = 0;
Benny Prijono224b4e22008-06-19 14:10:28 +00001593 }
1594
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001595#if 0
1596 // Since r3512, old-style hold should have got transport, created by
1597 // pjsua_media_channel_init() in initial offer/answer or remote reoffer.
Benny Prijono224b4e22008-06-19 14:10:28 +00001598 /* Create media if it's not created. This could happen when call is
Benny Prijono0bc99a92011-03-17 04:34:43 +00001599 * currently on-hold (with the old style hold)
Benny Prijono224b4e22008-06-19 14:10:28 +00001600 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001601 if (call->media[call->audio_idx].tp == NULL) {
Benny Prijono224b4e22008-06-19 14:10:28 +00001602 pjsip_role_e role;
1603 role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
1604 status = pjsua_media_channel_init(call_id, role, call->secure_level,
Benny Prijono0bc99a92011-03-17 04:34:43 +00001605 pool, rem_sdp, sip_err_code);
Benny Prijono224b4e22008-06-19 14:10:28 +00001606 if (status != PJ_SUCCESS)
1607 return status;
1608 }
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001609#endif
Benny Prijono224b4e22008-06-19 14:10:28 +00001610
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001611 /* Get SDP negotiator state */
1612 if (call->inv && call->inv->neg)
1613 sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg);
1614
Benny Prijono0bc99a92011-03-17 04:34:43 +00001615 /* Get one address to use in the origin field */
1616 pj_bzero(&origin, sizeof(origin));
1617 for (mi=0; mi<call->med_cnt; ++mi) {
1618 pjmedia_transport_info tpinfo;
Benny Prijono617c5bc2007-04-02 19:51:21 +00001619
Benny Prijono0bc99a92011-03-17 04:34:43 +00001620 if (call->media[mi].tp == NULL)
1621 continue;
1622
1623 pjmedia_transport_info_init(&tpinfo);
1624 pjmedia_transport_get_info(call->media[mi].tp, &tpinfo);
1625 pj_sockaddr_cp(&origin, &tpinfo.sock_info.rtp_addr_name);
1626 break;
Benny Prijono25b2ea12008-01-24 19:20:54 +00001627 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00001628
Benny Prijono0bc99a92011-03-17 04:34:43 +00001629 /* Create the base (blank) SDP */
1630 status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, pool, NULL,
1631 &origin, &sdp);
1632 if (status != PJ_SUCCESS)
1633 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001634
Benny Prijono0bc99a92011-03-17 04:34:43 +00001635 /* Process each media line */
1636 for (mi=0; mi<call->med_cnt; ++mi) {
1637 pjsua_call_media *call_med = &call->media[mi];
1638 pjmedia_sdp_media *m = NULL;
1639 pjmedia_transport_info tpinfo;
1640
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001641 if (rem_sdp && mi >= rem_sdp->media_count) {
1642 /* Remote might have removed some media lines. */
1643 break;
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001644 }
1645
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001646 if (call_med->tp == NULL || call_med->tp_st == PJSUA_MED_TP_DISABLED)
1647 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001648 /*
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001649 * This media is disabled. Just create a valid SDP with zero
Benny Prijono0bc99a92011-03-17 04:34:43 +00001650 * port.
1651 */
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001652 if (rem_sdp) {
1653 /* Just clone the remote media and deactivate it */
1654 m = pjmedia_sdp_media_clone_deactivate(pool,
1655 rem_sdp->media[mi]);
1656 } else {
1657 m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
1658 m->desc.transport = pj_str("RTP/AVP");
1659 m->desc.fmt_count = 1;
1660 m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
1661 m->conn->net_type = pj_str("IN");
1662 m->conn->addr_type = pj_str("IP4");
1663 m->conn->addr = pj_str("127.0.0.1");
Benny Prijonoa310bd22008-06-27 21:19:44 +00001664
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00001665 switch (call_med->type) {
1666 case PJMEDIA_TYPE_AUDIO:
1667 m->desc.media = pj_str("audio");
1668 m->desc.fmt[0] = pj_str("0");
1669 break;
1670 case PJMEDIA_TYPE_VIDEO:
1671 m->desc.media = pj_str("video");
1672 m->desc.fmt[0] = pj_str("31");
1673 break;
1674 default:
1675 if (rem_sdp) {
1676 pj_strdup(pool, &m->desc.media,
1677 &rem_sdp->media[mi]->desc.media);
1678 pj_strdup(pool, &m->desc.fmt[0],
1679 &rem_sdp->media[mi]->desc.fmt[0]);
1680 } else {
1681 pj_assert(!"Invalid call_med media type");
1682 return PJ_EBUG;
1683 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001684 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001685 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00001686
1687 sdp->media[sdp->media_count++] = m;
1688 continue;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001689 }
1690
Benny Prijono0bc99a92011-03-17 04:34:43 +00001691 /* Get transport address info */
1692 pjmedia_transport_info_init(&tpinfo);
1693 pjmedia_transport_get_info(call_med->tp, &tpinfo);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001694
Benny Prijono0bc99a92011-03-17 04:34:43 +00001695 /* Ask pjmedia endpoint to create SDP media line */
1696 switch (call_med->type) {
1697 case PJMEDIA_TYPE_AUDIO:
1698 status = pjmedia_endpt_create_audio_sdp(pjsua_var.med_endpt, pool,
1699 &tpinfo.sock_info, 0, &m);
1700 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00001701#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00001702 case PJMEDIA_TYPE_VIDEO:
1703 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
1704 &tpinfo.sock_info, 0, &m);
1705 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00001706#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00001707 default:
1708 pj_assert(!"Invalid call_med media type");
1709 return PJ_EBUG;
1710 }
Benny Prijonoa310bd22008-06-27 21:19:44 +00001711
Benny Prijono0bc99a92011-03-17 04:34:43 +00001712 if (status != PJ_SUCCESS)
1713 return status;
1714
1715 sdp->media[sdp->media_count++] = m;
1716
1717 /* Give to transport */
1718 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
1719 sdp, rem_sdp, mi);
1720 if (status != PJ_SUCCESS) {
1721 if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
1722 return status;
Benny Prijonoa310bd22008-06-27 21:19:44 +00001723 }
Nanang Izzuddin91ba2a22011-04-11 19:59:09 +00001724
1725 /* Copy c= line of the first media to session level,
1726 * if there's none.
1727 */
1728 if (sdp->conn == NULL) {
1729 sdp->conn = pjmedia_sdp_conn_clone(pool, m->conn);
Benny Prijonoa310bd22008-06-27 21:19:44 +00001730 }
1731 }
1732
Benny Prijono6ba8c542007-10-16 01:34:14 +00001733 /* Add NAT info in the SDP */
1734 if (pjsua_var.ua_cfg.nat_type_in_sdp) {
1735 pjmedia_sdp_attr *a;
1736 pj_str_t value;
1737 char nat_info[80];
1738
1739 value.ptr = nat_info;
1740 if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
1741 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1742 "%d", pjsua_var.nat_type);
1743 } else {
1744 const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
1745 value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
1746 "%d %s",
1747 pjsua_var.nat_type,
1748 type_name);
1749 }
1750
1751 a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
1752
1753 pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
1754
1755 }
1756
Benny Prijonoc97608e2007-03-23 16:34:20 +00001757
Benny Prijono0bc99a92011-03-17 04:34:43 +00001758#if DISABLED_FOR_TICKET_1185 && defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001759 /* Check if SRTP is in optional mode and configured to use duplicated
1760 * media, i.e: secured and unsecured version, in the SDP offer.
1761 */
1762 if (!rem_sdp &&
1763 pjsua_var.acc[call->acc_id].cfg.use_srtp == PJMEDIA_SRTP_OPTIONAL &&
1764 pjsua_var.acc[call->acc_id].cfg.srtp_optional_dup_offer)
1765 {
1766 unsigned i;
1767
1768 for (i = 0; i < sdp->media_count; ++i) {
1769 pjmedia_sdp_media *m = sdp->media[i];
1770
Benny Prijono0bc99a92011-03-17 04:34:43 +00001771 /* Check if this media is unsecured but has SDP "crypto"
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001772 * attribute.
1773 */
1774 if (pj_stricmp2(&m->desc.transport, "RTP/AVP") == 0 &&
1775 pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL)
1776 {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001777 if (i == (unsigned)call->audio_idx &&
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001778 sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE)
1779 {
1780 /* This is a session update, and peer has chosen the
1781 * unsecured version, so let's make this unsecured too.
1782 */
1783 pjmedia_sdp_media_remove_all_attr(m, "crypto");
1784 } else {
1785 /* This is new offer, duplicate media so we'll have
1786 * secured (with "RTP/SAVP" transport) and and unsecured
1787 * versions.
1788 */
1789 pjmedia_sdp_media *new_m;
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001790
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001791 /* Duplicate this media and apply secured transport */
1792 new_m = pjmedia_sdp_media_clone(pool, m);
1793 pj_strdup2(pool, &new_m->desc.transport, "RTP/SAVP");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001794
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001795 /* Remove the "crypto" attribute in the unsecured media */
1796 pjmedia_sdp_media_remove_all_attr(m, "crypto");
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001797
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001798 /* Insert the new media before the unsecured media */
1799 if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001800 pj_array_insert(sdp->media, sizeof(new_m),
Nanang Izzuddin1e952a82010-10-05 16:32:04 +00001801 sdp->media_count, i, &new_m);
1802 ++sdp->media_count;
1803 ++i;
1804 }
Nanang Izzuddind89cc3a2010-05-13 05:22:51 +00001805 }
1806 }
1807 }
1808 }
1809#endif
1810
Benny Prijonoc97608e2007-03-23 16:34:20 +00001811 *p_sdp = sdp;
1812 return PJ_SUCCESS;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001813}
1814
1815
1816static void stop_media_session(pjsua_call_id call_id)
1817{
1818 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001819 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001820
Benny Prijonob90fd382011-09-18 14:59:56 +00001821 pj_log_push_indent();
1822
Benny Prijono0bc99a92011-03-17 04:34:43 +00001823 for (mi=0; mi<call->med_cnt; ++mi) {
1824 pjsua_call_media *call_med = &call->media[mi];
1825
1826 if (call_med->type == PJMEDIA_TYPE_AUDIO) {
1827 pjmedia_stream *strm = call_med->strm.a.stream;
1828 pjmedia_rtcp_stat stat;
1829
1830 if (strm) {
1831 if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) {
1832 if (pjsua_var.mconf) {
1833 pjsua_conf_remove_port(call_med->strm.a.conf_slot);
1834 }
1835 call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
1836 }
1837
1838 if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
1839 (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS))
1840 {
1841 /* Save RTP timestamp & sequence, so when media session is
1842 * restarted, those values will be restored as the initial
1843 * RTP timestamp & sequence of the new media session. So in
1844 * the same call session, RTP timestamp and sequence are
1845 * guaranteed to be contigue.
1846 */
1847 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
1848 call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
1849 call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
1850 }
1851
1852 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
1853 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, strm, mi);
1854 }
1855
1856 pjmedia_stream_destroy(strm);
1857 call_med->strm.a.stream = NULL;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001858 }
Nanang Izzuddinfd461eb2008-06-09 09:35:59 +00001859 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00001860
1861#if PJMEDIA_HAS_VIDEO
1862 else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
Nanang Izzuddin62053a62011-07-12 11:08:32 +00001863 stop_video_stream(call_med);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001864 }
Nanang Izzuddin50fae732011-03-22 09:49:23 +00001865#endif
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00001866
1867 PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed",
1868 call_id, mi));
Benny Prijono0bc99a92011-03-17 04:34:43 +00001869 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001870 }
Benny Prijonob90fd382011-09-18 14:59:56 +00001871
1872 pj_log_pop_indent();
Benny Prijonoc97608e2007-03-23 16:34:20 +00001873}
1874
1875pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
1876{
1877 pjsua_call *call = &pjsua_var.calls[call_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00001878 unsigned mi;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001879
Benny Prijonob90fd382011-09-18 14:59:56 +00001880 PJ_LOG(4,(THIS_FILE, "Call %d: deinitializing media..", call_id));
1881 pj_log_push_indent();
1882
Benny Prijonoc97608e2007-03-23 16:34:20 +00001883 stop_media_session(call_id);
1884
Benny Prijono0bc99a92011-03-17 04:34:43 +00001885 for (mi=0; mi<call->med_cnt; ++mi) {
1886 pjsua_call_media *call_med = &call->media[mi];
Benny Prijonoc97608e2007-03-23 16:34:20 +00001887
Benny Prijono0bc99a92011-03-17 04:34:43 +00001888 if (call_med->tp_st != PJSUA_MED_TP_IDLE) {
1889 pjmedia_transport_media_stop(call_med->tp);
1890 call_med->tp_st = PJSUA_MED_TP_IDLE;
1891 }
1892
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001893 //if (call_med->tp_orig && call_med->tp &&
1894 // call_med->tp != call_med->tp_orig)
1895 //{
1896 // pjmedia_transport_close(call_med->tp);
1897 // call_med->tp = call_med->tp_orig;
1898 //}
1899 if (call_med->tp) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00001900 pjmedia_transport_close(call_med->tp);
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00001901 call_med->tp = call_med->tp_orig = NULL;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001902 }
Benny Prijonod8179652008-01-23 20:39:07 +00001903 }
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00001904
1905 check_snd_dev_idle();
Benny Prijonob90fd382011-09-18 14:59:56 +00001906 pj_log_pop_indent();
Nanang Izzuddin670f71b2009-01-20 14:05:54 +00001907
Benny Prijonoc97608e2007-03-23 16:34:20 +00001908 return PJ_SUCCESS;
1909}
1910
1911
1912/*
1913 * DTMF callback from the stream.
1914 */
1915static void dtmf_callback(pjmedia_stream *strm, void *user_data,
1916 int digit)
1917{
1918 PJ_UNUSED_ARG(strm);
1919
Benny Prijonob90fd382011-09-18 14:59:56 +00001920 pj_log_push_indent();
1921
Benny Prijono0c068262008-02-14 14:38:52 +00001922 /* For discussions about call mutex protection related to this
1923 * callback, please see ticket #460:
1924 * http://trac.pjsip.org/repos/ticket/460#comment:4
1925 */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001926 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
1927 pjsua_call_id call_id;
1928
Benny Prijonod8179652008-01-23 20:39:07 +00001929 call_id = (pjsua_call_id)(long)user_data;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001930 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
1931 }
Benny Prijonob90fd382011-09-18 14:59:56 +00001932
1933 pj_log_pop_indent();
Benny Prijonoc97608e2007-03-23 16:34:20 +00001934}
1935
1936
Benny Prijono0bc99a92011-03-17 04:34:43 +00001937static pj_status_t audio_channel_update(pjsua_call_media *call_med,
1938 pj_pool_t *tmp_pool,
1939 const pjmedia_sdp_session *local_sdp,
1940 const pjmedia_sdp_session *remote_sdp)
Benny Prijonoc97608e2007-03-23 16:34:20 +00001941{
Benny Prijono0bc99a92011-03-17 04:34:43 +00001942 pjsua_call *call = call_med->call;
1943 pjmedia_stream_info the_si, *si = &the_si;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001944 pjmedia_port *media_port;
Benny Prijono0bc99a92011-03-17 04:34:43 +00001945 unsigned strm_idx = call_med->idx;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001946 pj_status_t status;
Benny Prijonob90fd382011-09-18 14:59:56 +00001947
1948 PJ_LOG(4,(THIS_FILE,"Audio channel update.."));
1949 pj_log_push_indent();
Benny Prijono0bc99a92011-03-17 04:34:43 +00001950
1951 status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
1952 local_sdp, remote_sdp, strm_idx);
Benny Prijonoc97608e2007-03-23 16:34:20 +00001953 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00001954 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001955
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001956 /* Check if no media is active */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001957 if (si->dir == PJMEDIA_DIR_NONE) {
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001958 /* Call media state */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001959 call_med->state = PJSUA_CALL_MEDIA_NONE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001960
Nanang Izzuddin99d69522008-08-04 15:01:38 +00001961 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001962 call_med->dir = PJMEDIA_DIR_NONE;
Benny Prijono68f9e4f2008-03-21 08:56:02 +00001963
Benny Prijonoc97608e2007-03-23 16:34:20 +00001964 } else {
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001965 pjmedia_transport_info tp_info;
1966
Benny Prijono224b4e22008-06-19 14:10:28 +00001967 /* Start/restart media transport */
Benny Prijono0bc99a92011-03-17 04:34:43 +00001968 status = pjmedia_transport_media_start(call_med->tp,
1969 tmp_pool, local_sdp,
1970 remote_sdp, strm_idx);
Benny Prijonod8179652008-01-23 20:39:07 +00001971 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00001972 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001973
Benny Prijono0bc99a92011-03-17 04:34:43 +00001974 call_med->tp_st = PJSUA_MED_TP_RUNNING;
Benny Prijono224b4e22008-06-19 14:10:28 +00001975
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001976 /* Get remote SRTP usage policy */
1977 pjmedia_transport_info_init(&tp_info);
Benny Prijono0bc99a92011-03-17 04:34:43 +00001978 pjmedia_transport_get_info(call_med->tp, &tp_info);
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001979 if (tp_info.specific_info_cnt > 0) {
Benny Prijonof5d9f1f2009-10-14 13:13:18 +00001980 unsigned i;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001981 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
1982 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
1983 {
1984 pjmedia_srtp_info *srtp_info =
1985 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
1986
Benny Prijono0bc99a92011-03-17 04:34:43 +00001987 call_med->rem_srtp_use = srtp_info->peer_use;
Nanang Izzuddin4375f902008-06-26 19:12:09 +00001988 break;
1989 }
1990 }
1991 }
1992
Benny Prijonoc97608e2007-03-23 16:34:20 +00001993 /* Override ptime, if this option is specified. */
1994 if (pjsua_var.media_cfg.ptime != 0) {
Benny Prijono91e567e2007-12-28 08:51:58 +00001995 si->param->setting.frm_per_pkt = (pj_uint8_t)
1996 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
1997 if (si->param->setting.frm_per_pkt == 0)
1998 si->param->setting.frm_per_pkt = 1;
Benny Prijonoc97608e2007-03-23 16:34:20 +00001999 }
2000
2001 /* Disable VAD, if this option is specified. */
2002 if (pjsua_var.media_cfg.no_vad) {
Benny Prijono91e567e2007-12-28 08:51:58 +00002003 si->param->setting.vad = 0;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002004 }
2005
2006
2007 /* Optionally, application may modify other stream settings here
2008 * (such as jitter buffer parameters, codec ptime, etc.)
2009 */
Benny Prijono91e567e2007-12-28 08:51:58 +00002010 si->jb_init = pjsua_var.media_cfg.jb_init;
2011 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
2012 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
2013 si->jb_max = pjsua_var.media_cfg.jb_max;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002014
Benny Prijono8147f402007-11-21 14:50:07 +00002015 /* Set SSRC */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002016 si->ssrc = call_med->ssrc;
Benny Prijono8147f402007-11-21 14:50:07 +00002017
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002018 /* Set RTP timestamp & sequence, normally these value are intialized
2019 * automatically when stream session created, but for some cases (e.g:
2020 * call reinvite, call update) timestamp and sequence need to be kept
2021 * contigue.
2022 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002023 si->rtp_ts = call_med->rtp_tx_ts;
2024 si->rtp_seq = call_med->rtp_tx_seq;
2025 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
Nanang Izzuddina815ceb2008-08-26 16:51:28 +00002026
Nanang Izzuddin5e39a2b2010-09-20 06:13:02 +00002027#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
2028 /* Enable/disable stream keep-alive and NAT hole punch. */
2029 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
2030#endif
2031
Benny Prijonoc97608e2007-03-23 16:34:20 +00002032 /* Create session based on session info. */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002033 status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,
2034 call_med->tp, NULL,
2035 &call_med->strm.a.stream);
2036 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002037 goto on_return;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002038 }
2039
2040 /* Start stream */
2041 status = pjmedia_stream_start(call_med->strm.a.stream);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002042 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002043 goto on_return;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002044 }
2045
2046 /* If DTMF callback is installed by application, install our
2047 * callback to the session.
2048 */
2049 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002050 pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
2051 &dtmf_callback,
2052 (void*)(long)(call->index));
Benny Prijonoc97608e2007-03-23 16:34:20 +00002053 }
2054
2055 /* Get the port interface of the first stream in the session.
2056 * We need the port interface to add to the conference bridge.
2057 */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002058 pjmedia_stream_get_port(call_med->strm.a.stream, &media_port);
Benny Prijonoc97608e2007-03-23 16:34:20 +00002059
Benny Prijonofc13bf62008-02-20 08:56:15 +00002060 /* Notify application about stream creation.
2061 * Note: application may modify media_port to point to different
2062 * media port
2063 */
2064 if (pjsua_var.ua_cfg.cb.on_stream_created) {
Benny Prijono0bc99a92011-03-17 04:34:43 +00002065 pjsua_var.ua_cfg.cb.on_stream_created(call->index,
2066 call_med->strm.a.stream,
2067 strm_idx, &media_port);
Benny Prijonofc13bf62008-02-20 08:56:15 +00002068 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002069
2070 /*
2071 * Add the call to conference bridge.
2072 */
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002073 {
2074 char tmp[PJSIP_MAX_URL_SIZE];
2075 pj_str_t port_name;
2076
2077 port_name.ptr = tmp;
2078 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
2079 call->inv->dlg->remote.info->uri,
2080 tmp, sizeof(tmp));
2081 if (port_name.slen < 1) {
2082 port_name = pj_str("call");
2083 }
Benny Prijono40d62b62009-08-12 17:53:47 +00002084 status = pjmedia_conf_add_port( pjsua_var.mconf,
2085 call->inv->pool_prov,
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002086 media_port,
2087 &port_name,
Benny Prijono0bc99a92011-03-17 04:34:43 +00002088 (unsigned*)
2089 &call_med->strm.a.conf_slot);
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002090 if (status != PJ_SUCCESS) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002091 goto on_return;
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002092 }
Benny Prijonoc97608e2007-03-23 16:34:20 +00002093 }
2094
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002095 /* Call media direction */
Benny Prijono0bc99a92011-03-17 04:34:43 +00002096 call_med->dir = si->dir;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002097
2098 /* Call media state */
2099 if (call->local_hold)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002100 call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
2101 else if (call_med->dir == PJMEDIA_DIR_DECODING)
2102 call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
Nanang Izzuddin99d69522008-08-04 15:01:38 +00002103 else
Benny Prijono0bc99a92011-03-17 04:34:43 +00002104 call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002105 }
2106
2107 /* Print info. */
2108 {
2109 char info[80];
2110 int info_len = 0;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002111 int len;
2112 const char *dir;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002113
Benny Prijono0bc99a92011-03-17 04:34:43 +00002114 switch (si->dir) {
2115 case PJMEDIA_DIR_NONE:
2116 dir = "inactive";
2117 break;
2118 case PJMEDIA_DIR_ENCODING:
2119 dir = "sendonly";
2120 break;
2121 case PJMEDIA_DIR_DECODING:
2122 dir = "recvonly";
2123 break;
2124 case PJMEDIA_DIR_ENCODING_DECODING:
2125 dir = "sendrecv";
2126 break;
2127 default:
2128 dir = "unknown";
2129 break;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002130 }
Benny Prijono0bc99a92011-03-17 04:34:43 +00002131 len = pj_ansi_sprintf( info+info_len,
2132 ", stream #%d: %.*s (%s)", strm_idx,
2133 (int)si->fmt.encoding_name.slen,
2134 si->fmt.encoding_name.ptr,
2135 dir);
2136 if (len > 0)
2137 info_len += len;
Benny Prijonob90fd382011-09-18 14:59:56 +00002138 PJ_LOG(4,(THIS_FILE,"Audio updated%s", info));
Benny Prijonoc97608e2007-03-23 16:34:20 +00002139 }
2140
Benny Prijonob90fd382011-09-18 14:59:56 +00002141on_return:
2142 pj_log_pop_indent();
2143 return status;
Benny Prijonoc97608e2007-03-23 16:34:20 +00002144}
2145
Benny Prijono0bc99a92011-03-17 04:34:43 +00002146pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
2147 const pjmedia_sdp_session *local_sdp,
2148 const pjmedia_sdp_session *remote_sdp)
2149{
2150 pjsua_call *call = &pjsua_var.calls[call_id];
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002151 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
Benny Prijono0bc99a92011-03-17 04:34:43 +00002152 pj_pool_t *tmp_pool = call->inv->pool_prov;
2153 unsigned mi;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002154 pj_bool_t got_media = PJ_FALSE;
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002155 pj_status_t status = PJ_SUCCESS;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002156
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002157 const pj_str_t STR_AUDIO = { "audio", 5 };
2158 const pj_str_t STR_VIDEO = { "video", 5 };
2159 pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
2160 unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
2161 pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
2162 unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
2163 pj_bool_t need_renego_sdp = PJ_FALSE;
2164
Benny Prijono0bc99a92011-03-17 04:34:43 +00002165 if (pjsua_get_state() != PJSUA_STATE_RUNNING)
2166 return PJ_EBUSY;
2167
Benny Prijonob90fd382011-09-18 14:59:56 +00002168 PJ_LOG(4,(THIS_FILE, "Call %d: updating media..", call_id));
2169 pj_log_push_indent();
2170
Benny Prijono0bc99a92011-03-17 04:34:43 +00002171 /* Destroy existing media session, if any. */
2172 stop_media_session(call->index);
2173
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002174 /* Call media count must be at least equal to SDP media. Note that
2175 * it may not be equal when remote removed any SDP media line.
2176 */
2177 pj_assert(call->med_cnt >= local_sdp->media_count);
2178
Benny Prijono0bc99a92011-03-17 04:34:43 +00002179 /* Reset audio_idx first */
2180 call->audio_idx = -1;
2181
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002182 /* Apply maximum audio/video count of the account */
2183 sort_media(local_sdp, &STR_AUDIO, acc->cfg.use_srtp,
2184 maudidx, &maudcnt);
2185#if PJMEDIA_HAS_VIDEO
2186 sort_media(local_sdp, &STR_VIDEO, acc->cfg.use_srtp,
2187 mvididx, &mvidcnt);
2188#else
2189 PJ_UNUSED_ARG(STR_VIDEO);
2190 mvidcnt = 0;
2191#endif
2192 if (maudcnt > acc->cfg.max_audio_cnt || mvidcnt > acc->cfg.max_video_cnt)
2193 {
2194 pjmedia_sdp_session *local_sdp2;
2195
2196 maudcnt = PJ_MIN(maudcnt, acc->cfg.max_audio_cnt);
2197 mvidcnt = PJ_MIN(mvidcnt, acc->cfg.max_video_cnt);
2198 local_sdp2 = pjmedia_sdp_session_clone(tmp_pool, local_sdp);
2199
2200 for (mi=0; mi < local_sdp2->media_count; ++mi) {
2201 pjmedia_sdp_media *m = local_sdp2->media[mi];
2202
2203 if (m->desc.port == 0 ||
2204 pj_memchr(maudidx, mi, maudcnt*sizeof(maudidx[0])) ||
2205 pj_memchr(mvididx, mi, mvidcnt*sizeof(mvididx[0])))
2206 {
2207 continue;
2208 }
2209
2210 /* Deactivate this media */
2211 pjmedia_sdp_media_deactivate(tmp_pool, m);
2212 }
2213
2214 local_sdp = local_sdp2;
2215 need_renego_sdp = PJ_TRUE;
2216 }
2217
Benny Prijono0bc99a92011-03-17 04:34:43 +00002218 /* Process each media stream */
2219 for (mi=0; mi < call->med_cnt; ++mi) {
2220 pjsua_call_media *call_med = &call->media[mi];
2221
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002222 if (mi >= local_sdp->media_count ||
2223 mi >= remote_sdp->media_count)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002224 {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002225 /* This may happen when remote removed any SDP media lines in
2226 * its re-offer.
2227 */
2228 continue;
2229#if 0
Benny Prijono0bc99a92011-03-17 04:34:43 +00002230 /* Something is wrong */
2231 PJ_LOG(1,(THIS_FILE, "Error updating media for call %d: "
2232 "invalid media index %d in SDP", call_id, mi));
Benny Prijonob90fd382011-09-18 14:59:56 +00002233 status = PJMEDIA_SDP_EINSDP;
2234 goto on_error;
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002235#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002236 }
2237
2238 switch (call_med->type) {
2239 case PJMEDIA_TYPE_AUDIO:
2240 status = audio_channel_update(call_med, tmp_pool,
2241 local_sdp, remote_sdp);
2242 if (call->audio_idx==-1 && status==PJ_SUCCESS &&
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002243 call_med->strm.a.stream)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002244 {
2245 call->audio_idx = mi;
2246 }
2247 break;
Nanang Izzuddin63b3c132011-07-19 11:11:07 +00002248#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
Benny Prijono0bc99a92011-03-17 04:34:43 +00002249 case PJMEDIA_TYPE_VIDEO:
Nanang Izzuddinbf26db12011-03-18 07:54:50 +00002250 status = video_channel_update(call_med, tmp_pool,
2251 local_sdp, remote_sdp);
Benny Prijono0bc99a92011-03-17 04:34:43 +00002252 break;
Nanang Izzuddin50fae732011-03-22 09:49:23 +00002253#endif
Benny Prijono0bc99a92011-03-17 04:34:43 +00002254 default:
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002255 status = PJMEDIA_EINVALIMEDIATYPE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002256 break;
2257 }
2258
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002259 /* Close the transport of deactivated media, need this here as media
2260 * can be deactivated by the SDP negotiation and the max media count
2261 * (account) setting.
2262 */
2263 if (local_sdp->media[mi]->desc.port==0 && call_med->tp) {
2264 pjmedia_transport_close(call_med->tp);
2265 call_med->tp = call_med->tp_orig = NULL;
2266 call_med->tp_st = PJSUA_MED_TP_IDLE;
2267 }
2268
Benny Prijono0bc99a92011-03-17 04:34:43 +00002269 if (status != PJ_SUCCESS) {
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002270 PJ_PERROR(1,(THIS_FILE, status, "Error updating media call%02d:%d",
Benny Prijono0bc99a92011-03-17 04:34:43 +00002271 call_id, mi));
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002272 } else {
2273 got_media = PJ_TRUE;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002274 }
2275 }
2276
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002277 /* Perform SDP re-negotiation if needed. */
2278 if (got_media && need_renego_sdp) {
2279 pjmedia_sdp_neg *neg = call->inv->neg;
2280
2281 /* This should only happen when we are the answerer. */
2282 PJ_ASSERT_RETURN(neg && !pjmedia_sdp_neg_was_answer_remote(neg),
2283 PJMEDIA_SDPNEG_EINSTATE);
2284
2285 status = pjmedia_sdp_neg_set_remote_offer(tmp_pool, neg, remote_sdp);
2286 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002287 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002288
2289 status = pjmedia_sdp_neg_set_local_answer(tmp_pool, neg, local_sdp);
2290 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002291 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002292
2293 status = pjmedia_sdp_neg_negotiate(tmp_pool, neg, 0);
2294 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00002295 goto on_error;
Nanang Izzuddind8c33d82011-08-18 18:30:55 +00002296 }
2297
Benny Prijonob90fd382011-09-18 14:59:56 +00002298 pj_log_pop_indent();
Nanang Izzuddinb6c239c2011-05-10 05:42:28 +00002299 return (got_media? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA);
Benny Prijonob90fd382011-09-18 14:59:56 +00002300
2301on_error:
2302 pj_log_pop_indent();
2303 return status;
Benny Prijono0bc99a92011-03-17 04:34:43 +00002304}
2305
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002306/*
2307 * Get maxinum number of conference ports.
2308 */
2309PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
2310{
2311 return pjsua_var.media_cfg.max_media_ports;
2312}
2313
2314
2315/*
2316 * Get current number of active ports in the bridge.
2317 */
2318PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
2319{
Benny Prijono38fb3ea2008-01-02 08:27:03 +00002320 unsigned ports[PJSUA_MAX_CONF_PORTS];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002321 unsigned count = PJ_ARRAY_SIZE(ports);
2322 pj_status_t status;
2323
2324 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
2325 if (status != PJ_SUCCESS)
2326 count = 0;
2327
2328 return count;
2329}
2330
2331
2332/*
2333 * Enumerate all conference ports.
2334 */
2335PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
2336 unsigned *count)
2337{
2338 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
2339}
2340
2341
2342/*
2343 * Get information about the specified conference port
2344 */
2345PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
2346 pjsua_conf_port_info *info)
2347{
2348 pjmedia_conf_port_info cinfo;
Benny Prijono0498d902006-06-19 14:49:14 +00002349 unsigned i;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002350 pj_status_t status;
2351
2352 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
2353 if (status != PJ_SUCCESS)
2354 return status;
2355
Benny Prijonoac623b32006-07-03 15:19:31 +00002356 pj_bzero(info, sizeof(*info));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002357 info->slot_id = id;
2358 info->name = cinfo.name;
2359 info->clock_rate = cinfo.clock_rate;
2360 info->channel_count = cinfo.channel_count;
2361 info->samples_per_frame = cinfo.samples_per_frame;
2362 info->bits_per_sample = cinfo.bits_per_sample;
2363
2364 /* Build array of listeners */
Benny Prijonoc78c3a32006-06-16 15:54:43 +00002365 info->listener_cnt = cinfo.listener_cnt;
2366 for (i=0; i<cinfo.listener_cnt; ++i) {
2367 info->listeners[i] = cinfo.listener_slots[i];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002368 }
2369
2370 return PJ_SUCCESS;
2371}
2372
2373
2374/*
Benny Prijonoe909eac2006-07-27 22:04:56 +00002375 * Add arbitrary media port to PJSUA's conference bridge.
2376 */
2377PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
2378 pjmedia_port *port,
2379 pjsua_conf_port_id *p_id)
2380{
2381 pj_status_t status;
2382
2383 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
2384 port, NULL, (unsigned*)p_id);
2385 if (status != PJ_SUCCESS) {
2386 if (p_id)
2387 *p_id = PJSUA_INVALID_ID;
2388 }
2389
2390 return status;
2391}
2392
2393
2394/*
2395 * Remove arbitrary slot from the conference bridge.
2396 */
2397PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
2398{
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002399 pj_status_t status;
2400
2401 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
2402 check_snd_dev_idle();
2403
2404 return status;
Benny Prijonoe909eac2006-07-27 22:04:56 +00002405}
2406
2407
2408/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002409 * Establish unidirectional media flow from souce to sink.
2410 */
2411PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
2412 pjsua_conf_port_id sink)
2413{
Benny Prijonob90fd382011-09-18 14:59:56 +00002414 pj_status_t status = PJ_SUCCESS;
2415
2416 PJ_LOG(4,(THIS_FILE, "%s connect: %d --> %d",
2417 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
2418 source, sink));
2419 pj_log_push_indent();
2420
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002421 /* If sound device idle timer is active, cancel it first. */
Benny Prijono0f711b42009-05-06 19:08:43 +00002422 PJSUA_LOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002423 if (pjsua_var.snd_idle_timer.id) {
2424 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
2425 pjsua_var.snd_idle_timer.id = PJ_FALSE;
2426 }
Benny Prijono0f711b42009-05-06 19:08:43 +00002427 PJSUA_UNLOCK();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002428
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002429
Benny Prijonof798e502009-03-09 13:08:16 +00002430 /* For audio switchboard (i.e. APS-Direct):
2431 * Check if sound device need to be reopened, i.e: its attributes
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002432 * (format, clock rate, channel count) must match to peer's.
2433 * Note that sound device can be reopened only if it doesn't have
2434 * any connection.
2435 */
Benny Prijonof798e502009-03-09 13:08:16 +00002436 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002437 pjmedia_conf_port_info port0_info;
2438 pjmedia_conf_port_info peer_info;
2439 unsigned peer_id;
2440 pj_bool_t need_reopen = PJ_FALSE;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002441
2442 peer_id = (source!=0)? source : sink;
2443 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
2444 &peer_info);
2445 pj_assert(status == PJ_SUCCESS);
2446
2447 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
2448 pj_assert(status == PJ_SUCCESS);
2449
2450 /* Check if sound device is instantiated. */
2451 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2452 !pjsua_var.no_snd);
2453
2454 /* Check if sound device need to reopen because it needs to modify
2455 * settings to match its peer. Sound device must be idle in this case
2456 * though.
2457 */
2458 if (!need_reopen &&
2459 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
2460 {
2461 need_reopen = (peer_info.format.id != port0_info.format.id ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002462 peer_info.format.det.aud.avg_bps !=
2463 port0_info.format.det.aud.avg_bps ||
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002464 peer_info.clock_rate != port0_info.clock_rate ||
Benny Prijonoc45d9512010-12-10 11:04:30 +00002465 peer_info.channel_count!=port0_info.channel_count);
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002466 }
2467
2468 if (need_reopen) {
Benny Prijonod65f78c2009-06-03 18:59:37 +00002469 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
Sauw Ming98766c72011-03-11 06:57:24 +00002470 pjmedia_snd_port_param param;
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002471
Benny Prijonod65f78c2009-06-03 18:59:37 +00002472 /* Create parameter based on peer info */
Sauw Ming98766c72011-03-11 06:57:24 +00002473 status = create_aud_param(&param.base, pjsua_var.cap_dev,
Benny Prijonod65f78c2009-06-03 18:59:37 +00002474 pjsua_var.play_dev,
2475 peer_info.clock_rate,
2476 peer_info.channel_count,
2477 peer_info.samples_per_frame,
2478 peer_info.bits_per_sample);
2479 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002480 pjsua_perror(THIS_FILE, "Error opening sound device",
2481 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002482 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002483 }
Benny Prijonof798e502009-03-09 13:08:16 +00002484
Benny Prijonod65f78c2009-06-03 18:59:37 +00002485 /* And peer format */
2486 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
Sauw Ming98766c72011-03-11 06:57:24 +00002487 param.base.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
2488 param.base.ext_fmt = peer_info.format;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002489 }
Benny Prijono10454dc2009-02-21 14:21:59 +00002490
Sauw Ming98766c72011-03-11 06:57:24 +00002491 param.options = 0;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002492 status = open_snd_dev(&param);
2493 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002494 pjsua_perror(THIS_FILE, "Error opening sound device",
2495 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002496 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002497 }
2498 } else {
2499 /* Null-audio */
Benny Prijonoc45d9512010-12-10 11:04:30 +00002500 status = pjsua_set_snd_dev(pjsua_var.cap_dev,
2501 pjsua_var.play_dev);
Benny Prijonod65f78c2009-06-03 18:59:37 +00002502 if (status != PJ_SUCCESS) {
Benny Prijonoc45d9512010-12-10 11:04:30 +00002503 pjsua_perror(THIS_FILE, "Error opening sound device",
2504 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002505 goto on_return;
Benny Prijonod65f78c2009-06-03 18:59:37 +00002506 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002507 }
Benny Prijono2d647722011-07-13 03:05:22 +00002508 } else if (pjsua_var.no_snd) {
2509 if (!pjsua_var.snd_is_on) {
2510 pjsua_var.snd_is_on = PJ_TRUE;
2511 /* Notify app */
2512 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2513 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2514 }
2515 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002516 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002517
Benny Prijonof798e502009-03-09 13:08:16 +00002518 } else {
2519 /* The bridge version */
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002520
Benny Prijonof798e502009-03-09 13:08:16 +00002521 /* Create sound port if none is instantiated */
2522 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
2523 !pjsua_var.no_snd)
2524 {
2525 pj_status_t status;
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002526
Benny Prijonof798e502009-03-09 13:08:16 +00002527 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
2528 if (status != PJ_SUCCESS) {
2529 pjsua_perror(THIS_FILE, "Error opening sound device", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002530 goto on_return;
Benny Prijonof798e502009-03-09 13:08:16 +00002531 }
Benny Prijono2d647722011-07-13 03:05:22 +00002532 } else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) {
2533 pjsua_var.snd_is_on = PJ_TRUE;
2534 /* Notify app */
2535 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
2536 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
2537 }
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002538 }
Benny Prijonof798e502009-03-09 13:08:16 +00002539 }
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00002540
Benny Prijonob90fd382011-09-18 14:59:56 +00002541 status = pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
2542
2543on_return:
2544 pj_log_pop_indent();
2545 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002546}
2547
2548
2549/*
2550 * Disconnect media flow from the source to destination port.
2551 */
2552PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
2553 pjsua_conf_port_id sink)
2554{
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002555 pj_status_t status;
2556
Benny Prijonob90fd382011-09-18 14:59:56 +00002557 PJ_LOG(4,(THIS_FILE, "%s disconnect: %d -x- %d",
2558 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
2559 source, sink));
2560 pj_log_push_indent();
2561
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002562 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002563 check_snd_dev_idle();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002564
Benny Prijonob90fd382011-09-18 14:59:56 +00002565 pj_log_pop_indent();
Nanang Izzuddin68559c32008-06-13 17:01:46 +00002566 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002567}
2568
2569
Benny Prijono6dd967c2006-12-26 02:27:14 +00002570/*
2571 * Adjust the signal level to be transmitted from the bridge to the
2572 * specified port by making it louder or quieter.
2573 */
2574PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
2575 float level)
2576{
2577 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
2578 (int)((level-1) * 128));
2579}
2580
2581/*
2582 * Adjust the signal level to be received from the specified port (to
2583 * the bridge) by making it louder or quieter.
2584 */
2585PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
2586 float level)
2587{
2588 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
2589 (int)((level-1) * 128));
2590}
2591
2592
2593/*
2594 * Get last signal level transmitted to or received from the specified port.
2595 */
2596PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
2597 unsigned *tx_level,
2598 unsigned *rx_level)
2599{
2600 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
2601 tx_level, rx_level);
2602}
2603
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002604/*****************************************************************************
2605 * File player.
2606 */
2607
Benny Prijonod5696da2007-07-17 16:25:45 +00002608static char* get_basename(const char *path, unsigned len)
2609{
2610 char *p = ((char*)path) + len;
2611
2612 if (len==0)
2613 return p;
2614
Benny Prijono1f61a8f2007-08-16 10:11:44 +00002615 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
Benny Prijonod5696da2007-07-17 16:25:45 +00002616
2617 return (p==path) ? p : p+1;
2618}
2619
2620
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002621/*
2622 * Create a file player, and automatically connect this player to
2623 * the conference bridge.
2624 */
2625PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
2626 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002627 pjsua_player_id *p_id)
2628{
2629 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002630 char path[PJ_MAXPATH];
Benny Prijonob90fd382011-09-18 14:59:56 +00002631 pj_pool_t *pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002632 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00002633 pj_status_t status = PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002634
2635 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2636 return PJ_ETOOMANY;
2637
Benny Prijonob90fd382011-09-18 14:59:56 +00002638 PJ_LOG(4,(THIS_FILE, "Creating file player: %.*s..",
2639 (int)filename->slen, filename->ptr));
2640 pj_log_push_indent();
2641
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002642 PJSUA_LOCK();
2643
2644 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2645 if (pjsua_var.player[file_id].port == NULL)
2646 break;
2647 }
2648
2649 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2650 /* This is unexpected */
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002651 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00002652 status = PJ_EBUG;
2653 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002654 }
2655
2656 pj_memcpy(path, filename->ptr, filename->slen);
2657 path[filename->slen] = '\0';
Benny Prijonod5696da2007-07-17 16:25:45 +00002658
2659 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2660 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002661 status = PJ_ENOMEM;
2662 goto on_error;
Benny Prijonod5696da2007-07-17 16:25:45 +00002663 }
2664
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00002665 status = pjmedia_wav_player_port_create(
2666 pool, path,
2667 pjsua_var.mconf_cfg.samples_per_frame *
Benny Prijonoc45d9512010-12-10 11:04:30 +00002668 1000 / pjsua_var.media_cfg.channel_count /
Nanang Izzuddin81e9bd52008-06-27 12:52:51 +00002669 pjsua_var.media_cfg.clock_rate,
2670 options, 0, &port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002671 if (status != PJ_SUCCESS) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002672 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002673 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002674 }
2675
Benny Prijono5297af92008-03-18 13:40:40 +00002676 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002677 port, filename, &slot);
2678 if (status != PJ_SUCCESS) {
2679 pjmedia_port_destroy(port);
Benny Prijono32e4f492007-01-24 00:44:26 +00002680 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
2681 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002682 goto on_error;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002683 }
2684
Benny Prijonoa66c3312007-01-21 23:12:40 +00002685 pjsua_var.player[file_id].type = 0;
Benny Prijonod5696da2007-07-17 16:25:45 +00002686 pjsua_var.player[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002687 pjsua_var.player[file_id].port = port;
2688 pjsua_var.player[file_id].slot = slot;
2689
2690 if (p_id) *p_id = file_id;
2691
2692 ++pjsua_var.player_cnt;
2693
2694 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00002695
2696 PJ_LOG(4,(THIS_FILE, "Player created, id=%d, slot=%d", file_id, slot));
2697
2698 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002699 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00002700
2701on_error:
2702 PJSUA_UNLOCK();
2703 if (pool) pj_pool_release(pool);
2704 pj_log_pop_indent();
2705 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002706}
2707
2708
2709/*
Benny Prijonoa66c3312007-01-21 23:12:40 +00002710 * Create a file playlist media port, and automatically add the port
2711 * to the conference bridge.
2712 */
2713PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
2714 unsigned file_count,
2715 const pj_str_t *label,
2716 unsigned options,
2717 pjsua_player_id *p_id)
2718{
2719 unsigned slot, file_id, ptime;
Benny Prijonob90fd382011-09-18 14:59:56 +00002720 pj_pool_t *pool = NULL;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002721 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00002722 pj_status_t status = PJ_SUCCESS;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002723
2724 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
2725 return PJ_ETOOMANY;
2726
Benny Prijonob90fd382011-09-18 14:59:56 +00002727 PJ_LOG(4,(THIS_FILE, "Creating playlist with %d file(s)..", file_count));
2728 pj_log_push_indent();
2729
Benny Prijonoa66c3312007-01-21 23:12:40 +00002730 PJSUA_LOCK();
2731
2732 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
2733 if (pjsua_var.player[file_id].port == NULL)
2734 break;
2735 }
2736
2737 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
2738 /* This is unexpected */
Benny Prijonoa66c3312007-01-21 23:12:40 +00002739 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00002740 status = PJ_EBUG;
2741 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002742 }
2743
2744
2745 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
2746 pjsua_var.media_cfg.clock_rate;
2747
Benny Prijonod5696da2007-07-17 16:25:45 +00002748 pool = pjsua_pool_create("playlist", 1000, 1000);
2749 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002750 status = PJ_ENOMEM;
2751 goto on_error;
Benny Prijonod5696da2007-07-17 16:25:45 +00002752 }
2753
2754 status = pjmedia_wav_playlist_create(pool, label,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002755 file_names, file_count,
2756 ptime, options, 0, &port);
2757 if (status != PJ_SUCCESS) {
Benny Prijonoa66c3312007-01-21 23:12:40 +00002758 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002759 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002760 }
2761
Benny Prijonod5696da2007-07-17 16:25:45 +00002762 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoa66c3312007-01-21 23:12:40 +00002763 port, &port->info.name, &slot);
2764 if (status != PJ_SUCCESS) {
2765 pjmedia_port_destroy(port);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002766 pjsua_perror(THIS_FILE, "Unable to add port", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002767 goto on_error;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002768 }
2769
2770 pjsua_var.player[file_id].type = 1;
Benny Prijonod5696da2007-07-17 16:25:45 +00002771 pjsua_var.player[file_id].pool = pool;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002772 pjsua_var.player[file_id].port = port;
2773 pjsua_var.player[file_id].slot = slot;
2774
2775 if (p_id) *p_id = file_id;
2776
2777 ++pjsua_var.player_cnt;
2778
2779 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00002780
2781 PJ_LOG(4,(THIS_FILE, "Playlist created, id=%d, slot=%d", file_id, slot));
2782
2783 pj_log_pop_indent();
2784
Benny Prijonoa66c3312007-01-21 23:12:40 +00002785 return PJ_SUCCESS;
2786
Benny Prijonob90fd382011-09-18 14:59:56 +00002787on_error:
2788 PJSUA_UNLOCK();
2789 if (pool) pj_pool_release(pool);
2790 pj_log_pop_indent();
2791
2792 return status;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002793}
2794
2795
2796/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002797 * Get conference port ID associated with player.
2798 */
2799PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
2800{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002801 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002802 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2803
2804 return pjsua_var.player[id].slot;
2805}
2806
Benny Prijono469b1522006-12-26 03:05:17 +00002807/*
2808 * Get the media port for the player.
2809 */
Benny Prijonobe41d862008-01-18 13:24:28 +00002810PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
Benny Prijono469b1522006-12-26 03:05:17 +00002811 pjmedia_port **p_port)
2812{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002813 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00002814 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2815 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
2816
2817 *p_port = pjsua_var.player[id].port;
2818
2819 return PJ_SUCCESS;
2820}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002821
2822/*
2823 * Set playback position.
2824 */
2825PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
2826 pj_uint32_t samples)
2827{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002828 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002829 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
Benny Prijonoa66c3312007-01-21 23:12:40 +00002830 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002831
2832 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
2833}
2834
2835
2836/*
2837 * Close the file, remove the player from the bridge, and free
2838 * resources associated with the file player.
2839 */
2840PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
2841{
Benny Prijonoa1e69682007-05-11 15:14:34 +00002842 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002843 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
2844
Benny Prijonob90fd382011-09-18 14:59:56 +00002845 PJ_LOG(4,(THIS_FILE, "Destroying player %d..", id));
2846 pj_log_push_indent();
2847
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002848 PJSUA_LOCK();
2849
2850 if (pjsua_var.player[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00002851 pjsua_conf_remove_port(pjsua_var.player[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002852 pjmedia_port_destroy(pjsua_var.player[id].port);
2853 pjsua_var.player[id].port = NULL;
2854 pjsua_var.player[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00002855 pj_pool_release(pjsua_var.player[id].pool);
2856 pjsua_var.player[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002857 pjsua_var.player_cnt--;
2858 }
2859
2860 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00002861 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002862
2863 return PJ_SUCCESS;
2864}
2865
2866
2867/*****************************************************************************
2868 * File recorder.
2869 */
2870
2871/*
2872 * Create a file recorder, and automatically connect this recorder to
2873 * the conference bridge.
2874 */
2875PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
Benny Prijono8f310522006-10-20 11:08:49 +00002876 unsigned enc_type,
2877 void *enc_param,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002878 pj_ssize_t max_size,
2879 unsigned options,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002880 pjsua_recorder_id *p_id)
2881{
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002882 enum Format
2883 {
2884 FMT_UNKNOWN,
2885 FMT_WAV,
2886 FMT_MP3,
2887 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002888 unsigned slot, file_id;
Benny Prijonoa66c3312007-01-21 23:12:40 +00002889 char path[PJ_MAXPATH];
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002890 pj_str_t ext;
Benny Prijono8f310522006-10-20 11:08:49 +00002891 int file_format;
Benny Prijonob90fd382011-09-18 14:59:56 +00002892 pj_pool_t *pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002893 pjmedia_port *port;
Benny Prijonob90fd382011-09-18 14:59:56 +00002894 pj_status_t status = PJ_SUCCESS;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002895
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002896 /* Filename must present */
2897 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
2898
Benny Prijono00cae612006-07-31 15:19:36 +00002899 /* Don't support max_size at present */
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002900 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002901
Benny Prijono8f310522006-10-20 11:08:49 +00002902 /* Don't support encoding type at present */
2903 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
Benny Prijono00cae612006-07-31 15:19:36 +00002904
Benny Prijonob90fd382011-09-18 14:59:56 +00002905 PJ_LOG(4,(THIS_FILE, "Creating recorder %.*s..",
2906 (int)filename->slen, filename->ptr));
2907 pj_log_push_indent();
2908
2909 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) {
2910 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002911 return PJ_ETOOMANY;
Benny Prijonob90fd382011-09-18 14:59:56 +00002912 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002913
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002914 /* Determine the file format */
2915 ext.ptr = filename->ptr + filename->slen - 4;
2916 ext.slen = 4;
2917
2918 if (pj_stricmp2(&ext, ".wav") == 0)
2919 file_format = FMT_WAV;
2920 else if (pj_stricmp2(&ext, ".mp3") == 0)
2921 file_format = FMT_MP3;
2922 else {
2923 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
2924 "determine file format for %.*s",
2925 (int)filename->slen, filename->ptr));
Benny Prijonob90fd382011-09-18 14:59:56 +00002926 pj_log_pop_indent();
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002927 return PJ_ENOTSUP;
2928 }
2929
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002930 PJSUA_LOCK();
2931
2932 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
2933 if (pjsua_var.recorder[file_id].port == NULL)
2934 break;
2935 }
2936
2937 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
2938 /* This is unexpected */
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002939 pj_assert(0);
Benny Prijonob90fd382011-09-18 14:59:56 +00002940 status = PJ_EBUG;
2941 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002942 }
2943
2944 pj_memcpy(path, filename->ptr, filename->slen);
2945 path[filename->slen] = '\0';
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002946
Benny Prijonod5696da2007-07-17 16:25:45 +00002947 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
2948 if (!pool) {
Benny Prijonob90fd382011-09-18 14:59:56 +00002949 status = PJ_ENOMEM;
2950 goto on_return;
Benny Prijonod5696da2007-07-17 16:25:45 +00002951 }
2952
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002953 if (file_format == FMT_WAV) {
Benny Prijonod5696da2007-07-17 16:25:45 +00002954 status = pjmedia_wav_writer_port_create(pool, path,
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002955 pjsua_var.media_cfg.clock_rate,
2956 pjsua_var.mconf_cfg.channel_count,
2957 pjsua_var.mconf_cfg.samples_per_frame,
2958 pjsua_var.mconf_cfg.bits_per_sample,
2959 options, 0, &port);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002960 } else {
Benny Prijonoc95a0f02007-04-09 07:06:08 +00002961 PJ_UNUSED_ARG(enc_param);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00002962 port = NULL;
2963 status = PJ_ENOTSUP;
2964 }
2965
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002966 if (status != PJ_SUCCESS) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002967 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00002968 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002969 }
2970
Benny Prijonod5696da2007-07-17 16:25:45 +00002971 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002972 port, filename, &slot);
2973 if (status != PJ_SUCCESS) {
2974 pjmedia_port_destroy(port);
Benny Prijonob90fd382011-09-18 14:59:56 +00002975 goto on_return;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002976 }
2977
2978 pjsua_var.recorder[file_id].port = port;
2979 pjsua_var.recorder[file_id].slot = slot;
Benny Prijonod5696da2007-07-17 16:25:45 +00002980 pjsua_var.recorder[file_id].pool = pool;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002981
2982 if (p_id) *p_id = file_id;
2983
2984 ++pjsua_var.rec_cnt;
2985
2986 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00002987
2988 PJ_LOG(4,(THIS_FILE, "Recorder created, id=%d, slot=%d", file_id, slot));
2989
2990 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002991 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00002992
2993on_return:
2994 PJSUA_UNLOCK();
2995 if (pool) pj_pool_release(pool);
2996 pj_log_pop_indent();
2997 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002998}
2999
3000
3001/*
3002 * Get conference port associated with recorder.
3003 */
3004PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
3005{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003006 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3007 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003008 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3009
3010 return pjsua_var.recorder[id].slot;
3011}
3012
Benny Prijono469b1522006-12-26 03:05:17 +00003013/*
3014 * Get the media port for the recorder.
3015 */
3016PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
3017 pjmedia_port **p_port)
3018{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003019 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3020 PJ_EINVAL);
Benny Prijono469b1522006-12-26 03:05:17 +00003021 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3022 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
3023
3024 *p_port = pjsua_var.recorder[id].port;
3025 return PJ_SUCCESS;
3026}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003027
3028/*
3029 * Destroy recorder (this will complete recording).
3030 */
3031PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
3032{
Benny Prijonoa1e69682007-05-11 15:14:34 +00003033 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
3034 PJ_EINVAL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003035 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
3036
Benny Prijonob90fd382011-09-18 14:59:56 +00003037 PJ_LOG(4,(THIS_FILE, "Destroying recorder %d..", id));
3038 pj_log_push_indent();
3039
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003040 PJSUA_LOCK();
3041
3042 if (pjsua_var.recorder[id].port) {
Nanang Izzuddin148fd392008-06-16 09:52:50 +00003043 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003044 pjmedia_port_destroy(pjsua_var.recorder[id].port);
3045 pjsua_var.recorder[id].port = NULL;
3046 pjsua_var.recorder[id].slot = 0xFFFF;
Benny Prijonod5696da2007-07-17 16:25:45 +00003047 pj_pool_release(pjsua_var.recorder[id].pool);
3048 pjsua_var.recorder[id].pool = NULL;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003049 pjsua_var.rec_cnt--;
3050 }
3051
3052 PJSUA_UNLOCK();
Benny Prijonob90fd382011-09-18 14:59:56 +00003053 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003054
3055 return PJ_SUCCESS;
3056}
3057
3058
3059/*****************************************************************************
3060 * Sound devices.
3061 */
3062
3063/*
3064 * Enum sound devices.
3065 */
Benny Prijonof798e502009-03-09 13:08:16 +00003066
3067PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003068 unsigned *count)
3069{
3070 unsigned i, dev_count;
3071
Benny Prijono10454dc2009-02-21 14:21:59 +00003072 dev_count = pjmedia_aud_dev_count();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003073
3074 if (dev_count > *count) dev_count = *count;
3075
3076 for (i=0; i<dev_count; ++i) {
Benny Prijono10454dc2009-02-21 14:21:59 +00003077 pj_status_t status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003078
Benny Prijono10454dc2009-02-21 14:21:59 +00003079 status = pjmedia_aud_dev_get_info(i, &info[i]);
3080 if (status != PJ_SUCCESS)
3081 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003082 }
3083
3084 *count = dev_count;
3085
3086 return PJ_SUCCESS;
3087}
Benny Prijonof798e502009-03-09 13:08:16 +00003088
3089
Benny Prijono10454dc2009-02-21 14:21:59 +00003090PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
3091 unsigned *count)
3092{
3093 unsigned i, dev_count;
3094
3095 dev_count = pjmedia_aud_dev_count();
3096
3097 if (dev_count > *count) dev_count = *count;
3098 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
3099
3100 for (i=0; i<dev_count; ++i) {
3101 pjmedia_aud_dev_info ai;
3102 pj_status_t status;
3103
3104 status = pjmedia_aud_dev_get_info(i, &ai);
3105 if (status != PJ_SUCCESS)
3106 return status;
3107
3108 strncpy(info[i].name, ai.name, sizeof(info[i].name));
3109 info[i].name[sizeof(info[i].name)-1] = '\0';
3110 info[i].input_count = ai.input_count;
3111 info[i].output_count = ai.output_count;
3112 info[i].default_samples_per_sec = ai.default_samples_per_sec;
3113 }
3114
3115 *count = dev_count;
3116
3117 return PJ_SUCCESS;
3118}
Benny Prijono10454dc2009-02-21 14:21:59 +00003119
Benny Prijonof798e502009-03-09 13:08:16 +00003120/* Create audio device parameter to open the device */
3121static pj_status_t create_aud_param(pjmedia_aud_param *param,
3122 pjmedia_aud_dev_index capture_dev,
3123 pjmedia_aud_dev_index playback_dev,
3124 unsigned clock_rate,
3125 unsigned channel_count,
3126 unsigned samples_per_frame,
3127 unsigned bits_per_sample)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003128{
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003129 pj_status_t status;
3130
Benny Prijono96e74f32009-02-22 12:00:12 +00003131 /* Normalize device ID with new convention about default device ID */
3132 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
3133 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
3134
Benny Prijono10454dc2009-02-21 14:21:59 +00003135 /* Create default parameters for the device */
Benny Prijonof798e502009-03-09 13:08:16 +00003136 status = pjmedia_aud_dev_default_param(capture_dev, param);
Benny Prijono10454dc2009-02-21 14:21:59 +00003137 if (status != PJ_SUCCESS) {
Benny Prijono96e74f32009-02-22 12:00:12 +00003138 pjsua_perror(THIS_FILE, "Error retrieving default audio "
3139 "device parameters", status);
Benny Prijono10454dc2009-02-21 14:21:59 +00003140 return status;
3141 }
Benny Prijonof798e502009-03-09 13:08:16 +00003142 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
3143 param->rec_id = capture_dev;
3144 param->play_id = playback_dev;
3145 param->clock_rate = clock_rate;
3146 param->channel_count = channel_count;
3147 param->samples_per_frame = samples_per_frame;
3148 param->bits_per_sample = bits_per_sample;
3149
3150 /* Update the setting with user preference */
3151#define update_param(cap, field) \
3152 if (pjsua_var.aud_param.flags & cap) { \
3153 param->flags |= cap; \
3154 param->field = pjsua_var.aud_param.field; \
3155 }
3156 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3157 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3158 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3159 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3160#undef update_param
3161
Benny Prijono10454dc2009-02-21 14:21:59 +00003162 /* Latency settings */
Benny Prijonof798e502009-03-09 13:08:16 +00003163 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
3164 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
3165 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
3166 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
3167
Benny Prijono10454dc2009-02-21 14:21:59 +00003168 /* EC settings */
3169 if (pjsua_var.media_cfg.ec_tail_len) {
Benny Prijonof798e502009-03-09 13:08:16 +00003170 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
3171 param->ec_enabled = PJ_TRUE;
3172 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
Benny Prijono10454dc2009-02-21 14:21:59 +00003173 } else {
Benny Prijonof798e502009-03-09 13:08:16 +00003174 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
Benny Prijono10454dc2009-02-21 14:21:59 +00003175 }
3176
Benny Prijonof798e502009-03-09 13:08:16 +00003177 return PJ_SUCCESS;
3178}
Benny Prijono26056d82006-10-11 16:03:41 +00003179
Benny Prijonof798e502009-03-09 13:08:16 +00003180/* Internal: the first time the audio device is opened (during app
3181 * startup), retrieve the audio settings such as volume level
3182 * so that aud_get_settings() will work.
3183 */
3184static pj_status_t update_initial_aud_param()
3185{
3186 pjmedia_aud_stream *strm;
3187 pjmedia_aud_param param;
3188 pj_status_t status;
Benny Prijono26056d82006-10-11 16:03:41 +00003189
Benny Prijonof798e502009-03-09 13:08:16 +00003190 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
Nanang Izzuddin3c1ae632008-08-21 15:04:20 +00003191
Benny Prijonof798e502009-03-09 13:08:16 +00003192 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono26056d82006-10-11 16:03:41 +00003193
Benny Prijonof798e502009-03-09 13:08:16 +00003194 status = pjmedia_aud_stream_get_param(strm, &param);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003195 if (status != PJ_SUCCESS) {
Benny Prijonof798e502009-03-09 13:08:16 +00003196 pjsua_perror(THIS_FILE, "Error audio stream "
3197 "device parameters", status);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003198 return status;
3199 }
3200
Benny Prijonof798e502009-03-09 13:08:16 +00003201#define update_saved_param(cap, field) \
3202 if (param.flags & cap) { \
3203 pjsua_var.aud_param.flags |= cap; \
3204 pjsua_var.aud_param.field = param.field; \
3205 }
3206
3207 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
3208 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
3209 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
3210 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
3211#undef update_saved_param
3212
3213 return PJ_SUCCESS;
3214}
3215
3216/* Get format name */
3217static const char *get_fmt_name(pj_uint32_t id)
3218{
3219 static char name[8];
3220
3221 if (id == PJMEDIA_FORMAT_L16)
3222 return "PCM";
3223 pj_memcpy(name, &id, 4);
3224 name[4] = '\0';
3225 return name;
3226}
3227
3228/* Open sound device with the setting. */
Sauw Ming98766c72011-03-11 06:57:24 +00003229static pj_status_t open_snd_dev(pjmedia_snd_port_param *param)
Benny Prijonof798e502009-03-09 13:08:16 +00003230{
3231 pjmedia_port *conf_port;
3232 pj_status_t status;
3233
3234 PJ_ASSERT_RETURN(param, PJ_EINVAL);
3235
3236 /* Check if NULL sound device is used */
Sauw Ming98766c72011-03-11 06:57:24 +00003237 if (NULL_SND_DEV_ID==param->base.rec_id ||
3238 NULL_SND_DEV_ID==param->base.play_id)
3239 {
Benny Prijonof798e502009-03-09 13:08:16 +00003240 return pjsua_set_null_snd_dev();
3241 }
3242
3243 /* Close existing sound port */
3244 close_snd_dev();
3245
Benny Prijono2d647722011-07-13 03:05:22 +00003246 /* Notify app */
3247 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3248 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
3249 }
3250
Benny Prijonof798e502009-03-09 13:08:16 +00003251 /* Create memory pool for sound device. */
3252 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3253 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3254
3255
3256 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
Sauw Ming98766c72011-03-11 06:57:24 +00003257 get_fmt_name(param->base.ext_fmt.id),
3258 param->base.clock_rate, param->base.channel_count,
3259 param->base.samples_per_frame / param->base.channel_count *
3260 1000 / param->base.clock_rate));
Benny Prijonob90fd382011-09-18 14:59:56 +00003261 pj_log_push_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003262
3263 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
Sauw Ming98766c72011-03-11 06:57:24 +00003264 param, &pjsua_var.snd_port);
Benny Prijonof798e502009-03-09 13:08:16 +00003265 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00003266 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003267
3268 /* Get the port0 of the conference bridge. */
3269 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3270 pj_assert(conf_port != NULL);
3271
3272 /* For conference bridge, resample if necessary if the bridge's
3273 * clock rate is different than the sound device's clock rate.
3274 */
3275 if (!pjsua_var.is_mswitch &&
Sauw Ming98766c72011-03-11 06:57:24 +00003276 param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM &&
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003277 PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate)
Benny Prijonof798e502009-03-09 13:08:16 +00003278 {
3279 pjmedia_port *resample_port;
3280 unsigned resample_opt = 0;
3281
3282 if (pjsua_var.media_cfg.quality >= 3 &&
3283 pjsua_var.media_cfg.quality <= 4)
3284 {
3285 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
3286 }
3287 else if (pjsua_var.media_cfg.quality < 3) {
3288 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
3289 }
3290
3291 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
3292 conf_port,
Sauw Ming98766c72011-03-11 06:57:24 +00003293 param->base.clock_rate,
Benny Prijonof798e502009-03-09 13:08:16 +00003294 resample_opt,
3295 &resample_port);
3296 if (status != PJ_SUCCESS) {
3297 char errmsg[PJ_ERR_MSG_SIZE];
3298 pj_strerror(status, errmsg, sizeof(errmsg));
3299 PJ_LOG(4, (THIS_FILE,
3300 "Error creating resample port: %s",
3301 errmsg));
3302 close_snd_dev();
Benny Prijonob90fd382011-09-18 14:59:56 +00003303 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003304 }
3305
3306 conf_port = resample_port;
3307 }
3308
3309 /* Otherwise for audio switchboard, the switch's port0 setting is
3310 * derived from the sound device setting, so update the setting.
3311 */
3312 if (pjsua_var.is_mswitch) {
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003313 pj_memcpy(&conf_port->info.fmt, &param->base.ext_fmt,
Benny Prijonoc45d9512010-12-10 11:04:30 +00003314 sizeof(conf_port->info.fmt));
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003315 conf_port->info.fmt.det.aud.clock_rate = param->base.clock_rate;
3316 conf_port->info.fmt.det.aud.frame_time_usec = param->base.samples_per_frame*
Benny Prijonoc45d9512010-12-10 11:04:30 +00003317 1000000 /
Nanang Izzuddinfe68f1d2011-07-19 03:42:28 +00003318 param->base.clock_rate;
3319 conf_port->info.fmt.det.aud.channel_count = param->base.channel_count;
Benny Prijonoc45d9512010-12-10 11:04:30 +00003320 conf_port->info.fmt.det.aud.bits_per_sample = 16;
Benny Prijonof798e502009-03-09 13:08:16 +00003321 }
3322
Benny Prijonoc45d9512010-12-10 11:04:30 +00003323
Benny Prijonof798e502009-03-09 13:08:16 +00003324 /* Connect sound port to the bridge */
Benny Prijono52a93912006-08-04 20:54:37 +00003325 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
3326 conf_port );
3327 if (status != PJ_SUCCESS) {
3328 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
3329 "sound device", status);
3330 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3331 pjsua_var.snd_port = NULL;
Benny Prijonob90fd382011-09-18 14:59:56 +00003332 goto on_error;
Benny Prijono52a93912006-08-04 20:54:37 +00003333 }
3334
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003335 /* Save the device IDs */
Sauw Ming98766c72011-03-11 06:57:24 +00003336 pjsua_var.cap_dev = param->base.rec_id;
3337 pjsua_var.play_dev = param->base.play_id;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003338
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003339 /* Update sound device name. */
Benny Prijonof798e502009-03-09 13:08:16 +00003340 {
3341 pjmedia_aud_dev_info rec_info;
3342 pjmedia_aud_stream *strm;
3343 pjmedia_aud_param si;
3344 pj_str_t tmp;
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003345
Benny Prijonof798e502009-03-09 13:08:16 +00003346 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3347 status = pjmedia_aud_stream_get_param(strm, &si);
3348 if (status == PJ_SUCCESS)
3349 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
Benny Prijonof3758ee2008-02-26 15:32:16 +00003350
Benny Prijonof798e502009-03-09 13:08:16 +00003351 if (status==PJ_SUCCESS) {
Sauw Ming98766c72011-03-11 06:57:24 +00003352 if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) {
Benny Prijonof798e502009-03-09 13:08:16 +00003353 char tmp_buf[128];
3354 int tmp_buf_len = sizeof(tmp_buf);
3355
3356 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
3357 "%s (%dKHz)",
3358 rec_info.name,
Sauw Ming98766c72011-03-11 06:57:24 +00003359 param->base.clock_rate/1000);
Benny Prijonof798e502009-03-09 13:08:16 +00003360 pj_strset(&tmp, tmp_buf, tmp_buf_len);
3361 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
3362 } else {
3363 pjmedia_conf_set_port0_name(pjsua_var.mconf,
3364 pj_cstr(&tmp, rec_info.name));
3365 }
3366 }
3367
3368 /* Any error is not major, let it through */
3369 status = PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003370 }
Benny Prijonof798e502009-03-09 13:08:16 +00003371
3372 /* If this is the first time the audio device is open, retrieve some
3373 * settings from the device (such as volume settings) so that the
3374 * pjsua_snd_get_setting() work.
3375 */
3376 if (pjsua_var.aud_open_cnt == 0) {
3377 update_initial_aud_param();
3378 ++pjsua_var.aud_open_cnt;
Benny Prijonof3758ee2008-02-26 15:32:16 +00003379 }
Benny Prijonoc53c6d72006-11-27 09:54:03 +00003380
Benny Prijonob90fd382011-09-18 14:59:56 +00003381 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003382 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003383
3384on_error:
3385 pj_log_pop_indent();
3386 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00003387}
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003388
Nanang Izzuddin8465c682009-03-04 17:23:25 +00003389
Benny Prijonof798e502009-03-09 13:08:16 +00003390/* Close existing sound device */
3391static void close_snd_dev(void)
3392{
Benny Prijonob90fd382011-09-18 14:59:56 +00003393 pj_log_push_indent();
3394
Benny Prijono2d647722011-07-13 03:05:22 +00003395 /* Notify app */
3396 if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3397 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0);
3398 }
3399
Benny Prijonof798e502009-03-09 13:08:16 +00003400 /* Close sound device */
3401 if (pjsua_var.snd_port) {
3402 pjmedia_aud_dev_info cap_info, play_info;
3403 pjmedia_aud_stream *strm;
3404 pjmedia_aud_param param;
3405
3406 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3407 pjmedia_aud_stream_get_param(strm, &param);
3408
3409 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
3410 cap_info.name[0] = '\0';
3411 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
3412 play_info.name[0] = '\0';
3413
3414 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
3415 "%s sound capture device",
3416 play_info.name, cap_info.name));
3417
3418 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
3419 pjmedia_snd_port_destroy(pjsua_var.snd_port);
3420 pjsua_var.snd_port = NULL;
3421 }
3422
3423 /* Close null sound device */
3424 if (pjsua_var.null_snd) {
3425 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
3426 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
3427 pjsua_var.null_snd = NULL;
3428 }
3429
3430 if (pjsua_var.snd_pool)
3431 pj_pool_release(pjsua_var.snd_pool);
3432 pjsua_var.snd_pool = NULL;
Benny Prijono2d647722011-07-13 03:05:22 +00003433 pjsua_var.snd_is_on = PJ_FALSE;
Benny Prijonob90fd382011-09-18 14:59:56 +00003434
3435 pj_log_pop_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003436}
3437
3438
3439/*
3440 * Select or change sound device. Application may call this function at
3441 * any time to replace current sound device.
3442 */
3443PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
3444 int playback_dev)
3445{
3446 unsigned alt_cr_cnt = 1;
3447 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
3448 unsigned i;
3449 pj_status_t status = -1;
3450
Benny Prijonob90fd382011-09-18 14:59:56 +00003451 PJ_LOG(4,(THIS_FILE, "Set sound device: capture=%d, playback=%d",
3452 capture_dev, playback_dev));
3453 pj_log_push_indent();
3454
Benny Prijono23ea21a2009-06-03 12:43:06 +00003455 /* Null-sound */
3456 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
Benny Prijonob90fd382011-09-18 14:59:56 +00003457 status = pjsua_set_null_snd_dev();
3458 pj_log_pop_indent();
3459 return status;
Benny Prijono23ea21a2009-06-03 12:43:06 +00003460 }
3461
Benny Prijonof798e502009-03-09 13:08:16 +00003462 /* Set default clock rate */
3463 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
3464 if (alt_cr[0] == 0)
3465 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
3466
3467 /* Allow retrying of different clock rate if we're using conference
3468 * bridge (meaning audio format is always PCM), otherwise lock on
3469 * to one clock rate.
3470 */
3471 if (pjsua_var.is_mswitch) {
3472 alt_cr_cnt = 1;
3473 } else {
3474 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
3475 }
3476
3477 /* Attempts to open the sound device with different clock rates */
3478 for (i=0; i<alt_cr_cnt; ++i) {
Sauw Ming98766c72011-03-11 06:57:24 +00003479 pjmedia_snd_port_param param;
Benny Prijonof798e502009-03-09 13:08:16 +00003480 unsigned samples_per_frame;
3481
3482 /* Create the default audio param */
3483 samples_per_frame = alt_cr[i] *
3484 pjsua_var.media_cfg.audio_frame_ptime *
3485 pjsua_var.media_cfg.channel_count / 1000;
Sauw Ming98766c72011-03-11 06:57:24 +00003486 status = create_aud_param(&param.base, capture_dev, playback_dev,
Benny Prijonof798e502009-03-09 13:08:16 +00003487 alt_cr[i], pjsua_var.media_cfg.channel_count,
3488 samples_per_frame, 16);
3489 if (status != PJ_SUCCESS)
Benny Prijonob90fd382011-09-18 14:59:56 +00003490 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003491
3492 /* Open! */
Sauw Ming98766c72011-03-11 06:57:24 +00003493 param.options = 0;
Benny Prijonof798e502009-03-09 13:08:16 +00003494 status = open_snd_dev(&param);
3495 if (status == PJ_SUCCESS)
3496 break;
3497 }
3498
3499 if (status != PJ_SUCCESS) {
3500 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003501 goto on_error;
Benny Prijonof798e502009-03-09 13:08:16 +00003502 }
3503
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003504 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00003505 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003506
Benny Prijonob90fd382011-09-18 14:59:56 +00003507 pj_log_pop_indent();
Benny Prijonof798e502009-03-09 13:08:16 +00003508 return PJ_SUCCESS;
Benny Prijonob90fd382011-09-18 14:59:56 +00003509
3510on_error:
3511 pj_log_pop_indent();
3512 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003513}
3514
3515
3516/*
Benny Prijonoebdf8772007-02-01 19:25:50 +00003517 * Get currently active sound devices. If sound devices has not been created
3518 * (for example when pjsua_start() is not called), it is possible that
3519 * the function returns PJ_SUCCESS with -1 as device IDs.
3520 */
3521PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
3522 int *playback_dev)
3523{
3524 if (capture_dev) {
3525 *capture_dev = pjsua_var.cap_dev;
3526 }
3527 if (playback_dev) {
3528 *playback_dev = pjsua_var.play_dev;
3529 }
3530
3531 return PJ_SUCCESS;
3532}
3533
3534
3535/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003536 * Use null sound device.
3537 */
3538PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
3539{
3540 pjmedia_port *conf_port;
3541 pj_status_t status;
3542
Benny Prijonob90fd382011-09-18 14:59:56 +00003543 PJ_LOG(4,(THIS_FILE, "Setting null sound device.."));
3544 pj_log_push_indent();
3545
3546
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003547 /* Close existing sound device */
3548 close_snd_dev();
3549
Benny Prijono2d647722011-07-13 03:05:22 +00003550 /* Notify app */
3551 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
3552 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
3553 }
3554
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003555 /* Create memory pool for sound device. */
3556 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
3557 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
3558
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003559 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
3560
3561 /* Get the port0 of the conference bridge. */
3562 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
3563 pj_assert(conf_port != NULL);
3564
3565 /* Create master port, connecting port0 of the conference bridge to
3566 * a null port.
3567 */
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003568 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003569 conf_port, 0, &pjsua_var.null_snd);
3570 if (status != PJ_SUCCESS) {
3571 pjsua_perror(THIS_FILE, "Unable to create null sound device",
3572 status);
Benny Prijonob90fd382011-09-18 14:59:56 +00003573 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003574 return status;
3575 }
3576
3577 /* Start the master port */
3578 status = pjmedia_master_port_start(pjsua_var.null_snd);
3579 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
3580
Nanang Izzuddin68559c32008-06-13 17:01:46 +00003581 pjsua_var.cap_dev = NULL_SND_DEV_ID;
3582 pjsua_var.play_dev = NULL_SND_DEV_ID;
3583
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003584 pjsua_var.no_snd = PJ_FALSE;
Benny Prijono2d647722011-07-13 03:05:22 +00003585 pjsua_var.snd_is_on = PJ_TRUE;
Benny Prijonoe25fe6f2009-07-16 17:52:08 +00003586
Benny Prijonob90fd382011-09-18 14:59:56 +00003587 pj_log_pop_indent();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003588 return PJ_SUCCESS;
3589}
3590
3591
Benny Prijonoe909eac2006-07-27 22:04:56 +00003592
3593/*
3594 * Use no device!
3595 */
3596PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
3597{
3598 /* Close existing sound device */
3599 close_snd_dev();
3600
3601 pjsua_var.no_snd = PJ_TRUE;
3602 return pjmedia_conf_get_master_port(pjsua_var.mconf);
3603}
3604
3605
Benny Prijonof20687a2006-08-04 18:27:19 +00003606/*
3607 * Configure the AEC settings of the sound port.
3608 */
Benny Prijono5da50432006-08-07 10:24:52 +00003609PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
Benny Prijonof20687a2006-08-04 18:27:19 +00003610{
3611 pjsua_var.media_cfg.ec_tail_len = tail_ms;
3612
3613 if (pjsua_var.snd_port)
Benny Prijono5da50432006-08-07 10:24:52 +00003614 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,
3615 tail_ms, options);
Benny Prijonof20687a2006-08-04 18:27:19 +00003616
3617 return PJ_SUCCESS;
3618}
3619
3620
3621/*
3622 * Get current AEC tail length.
3623 */
Benny Prijono22dfe592006-08-06 12:07:13 +00003624PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
Benny Prijonof20687a2006-08-04 18:27:19 +00003625{
3626 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
3627 return PJ_SUCCESS;
3628}
3629
Nanang Izzuddinfe02a062009-02-18 14:28:49 +00003630
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003631/*
Benny Prijonof798e502009-03-09 13:08:16 +00003632 * Check whether the sound device is currently active.
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003633 */
Benny Prijonof798e502009-03-09 13:08:16 +00003634PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003635{
Benny Prijonof798e502009-03-09 13:08:16 +00003636 return pjsua_var.snd_port != NULL;
3637}
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003638
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003639
Benny Prijonof798e502009-03-09 13:08:16 +00003640/*
3641 * Configure sound device setting to the sound device being used.
3642 */
3643PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
3644 const void *pval,
3645 pj_bool_t keep)
3646{
Benny Prijono09b0ff62009-03-10 12:07:51 +00003647 pj_status_t status;
3648
Benny Prijonof798e502009-03-09 13:08:16 +00003649 /* Check if we are allowed to set the cap */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003650 if ((cap & pjsua_var.aud_svmask) == 0) {
Benny Prijonof798e502009-03-09 13:08:16 +00003651 return PJMEDIA_EAUD_INVCAP;
3652 }
3653
Benny Prijono09b0ff62009-03-10 12:07:51 +00003654 /* If sound is active, set it immediately */
Benny Prijonof798e502009-03-09 13:08:16 +00003655 if (pjsua_snd_is_active()) {
Benny Prijonof798e502009-03-09 13:08:16 +00003656 pjmedia_aud_stream *strm;
3657
3658 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003659 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
Benny Prijonof798e502009-03-09 13:08:16 +00003660 } else {
Benny Prijono09b0ff62009-03-10 12:07:51 +00003661 status = PJ_SUCCESS;
Benny Prijonof798e502009-03-09 13:08:16 +00003662 }
Benny Prijono09b0ff62009-03-10 12:07:51 +00003663
3664 if (status != PJ_SUCCESS)
3665 return status;
3666
3667 /* Save in internal param for later device open */
3668 if (keep) {
3669 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
3670 cap, pval);
3671 }
3672
3673 return status;
Benny Prijonof798e502009-03-09 13:08:16 +00003674}
3675
3676/*
3677 * Retrieve a sound device setting.
3678 */
3679PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
3680 void *pval)
3681{
3682 /* If sound device has never been opened before, open it to
3683 * retrieve the initial setting from the device (e.g. audio
3684 * volume)
3685 */
Benny Prijono09b0ff62009-03-10 12:07:51 +00003686 if (pjsua_var.aud_open_cnt==0) {
3687 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
Benny Prijonof798e502009-03-09 13:08:16 +00003688 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
Benny Prijono09b0ff62009-03-10 12:07:51 +00003689 close_snd_dev();
3690 }
Benny Prijonof798e502009-03-09 13:08:16 +00003691
3692 if (pjsua_snd_is_active()) {
3693 /* Sound is active, retrieve from device directly */
3694 pjmedia_aud_stream *strm;
3695
3696 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
3697 return pjmedia_aud_stream_get_cap(strm, cap, pval);
3698 } else {
3699 /* Otherwise retrieve from internal param */
3700 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
3701 cap, pval);
3702 }
Nanang Izzuddin0cb3b022009-02-27 17:37:35 +00003703}
3704
3705
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003706/*****************************************************************************
3707 * Codecs.
3708 */
3709
3710/*
3711 * Enum all supported codecs in the system.
3712 */
3713PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
3714 unsigned *p_count )
3715{
3716 pjmedia_codec_mgr *codec_mgr;
3717 pjmedia_codec_info info[32];
3718 unsigned i, count, prio[32];
3719 pj_status_t status;
3720
3721 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3722 count = PJ_ARRAY_SIZE(info);
3723 status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
3724 if (status != PJ_SUCCESS) {
3725 *p_count = 0;
3726 return status;
3727 }
3728
3729 if (count > *p_count) count = *p_count;
3730
3731 for (i=0; i<count; ++i) {
Nanang Izzuddin56b2ce42011-04-06 13:55:01 +00003732 pj_bzero(&id[i], sizeof(pjsua_codec_info));
3733
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003734 pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
3735 id[i].codec_id = pj_str(id[i].buf_);
3736 id[i].priority = (pj_uint8_t) prio[i];
3737 }
3738
3739 *p_count = count;
3740
3741 return PJ_SUCCESS;
3742}
3743
3744
3745/*
3746 * Change codec priority.
3747 */
3748PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
3749 pj_uint8_t priority )
3750{
Benny Prijono88accae2008-06-26 15:48:14 +00003751 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003752 pjmedia_codec_mgr *codec_mgr;
3753
3754 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3755
Benny Prijono88accae2008-06-26 15:48:14 +00003756 if (codec_id->slen==1 && *codec_id->ptr=='*')
3757 codec_id = &all;
3758
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003759 return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
3760 priority);
3761}
3762
3763
3764/*
3765 * Get codec parameters.
3766 */
3767PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
3768 pjmedia_codec_param *param )
3769{
Benny Prijono88accae2008-06-26 15:48:14 +00003770 const pj_str_t all = { NULL, 0 };
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003771 const pjmedia_codec_info *info;
3772 pjmedia_codec_mgr *codec_mgr;
3773 unsigned count = 1;
3774 pj_status_t status;
3775
3776 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3777
Benny Prijono88accae2008-06-26 15:48:14 +00003778 if (codec_id->slen==1 && *codec_id->ptr=='*')
3779 codec_id = &all;
3780
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003781 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
3782 &count, &info, NULL);
3783 if (status != PJ_SUCCESS)
3784 return status;
3785
3786 if (count != 1)
Nanang Izzuddin50fae732011-03-22 09:49:23 +00003787 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003788
3789 status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
3790 return status;
3791}
3792
3793
3794/*
3795 * Set codec parameters.
3796 */
Nanang Izzuddin06839e72010-01-27 11:48:31 +00003797PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003798 const pjmedia_codec_param *param)
3799{
Nanang Izzuddin06839e72010-01-27 11:48:31 +00003800 const pjmedia_codec_info *info[2];
3801 pjmedia_codec_mgr *codec_mgr;
3802 unsigned count = 2;
3803 pj_status_t status;
3804
3805 codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
3806
3807 status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
3808 &count, info, NULL);
3809 if (status != PJ_SUCCESS)
3810 return status;
3811
3812 /* Codec ID should be specific, except for G.722.1 */
3813 if (count > 1 &&
3814 pj_strnicmp2(codec_id, "G7221/16", 8) != 0 &&
3815 pj_strnicmp2(codec_id, "G7221/32", 8) != 0)
3816 {
3817 pj_assert(!"Codec ID is not specific");
3818 return PJ_ETOOMANY;
3819 }
3820
3821 status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param);
3822 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003823}
Nanang Izzuddin50fae732011-03-22 09:49:23 +00003824
3825