blob: c6de7c0749efded4f321577718d9c8f12f42d137 [file] [log] [blame]
Benny Prijono9f468d12011-07-07 07:46:33 +00001/* $Id$ */
2/*
3 * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjsua-lib/pjsua.h>
20#include <pjsua-lib/pjsua_internal.h>
21
22#define THIS_FILE "pjsua_vid.c"
23
24#if PJSUA_HAS_VIDEO
25
Nanang Izzuddin62053a62011-07-12 11:08:32 +000026static void free_vid_win(pjsua_vid_win_id wid);
27
Benny Prijono9f468d12011-07-07 07:46:33 +000028/*****************************************************************************
29 * pjsua video subsystem.
30 */
31pj_status_t pjsua_vid_subsys_init(void)
32{
33 unsigned i;
34 pj_status_t status;
35
36 status = pjmedia_video_format_mgr_create(pjsua_var.pool, 64, 0, NULL);
37 if (status != PJ_SUCCESS) {
38 PJ_PERROR(1,(THIS_FILE, status,
39 "Error creating PJMEDIA video format manager"));
40 return status;
41 }
42
43 status = pjmedia_converter_mgr_create(pjsua_var.pool, NULL);
44 if (status != PJ_SUCCESS) {
45 PJ_PERROR(1,(THIS_FILE, status,
46 "Error creating PJMEDIA converter manager"));
47 return status;
48 }
49
50 status = pjmedia_vid_codec_mgr_create(pjsua_var.pool, NULL);
51 if (status != PJ_SUCCESS) {
52 PJ_PERROR(1,(THIS_FILE, status,
53 "Error creating PJMEDIA video codec manager"));
54 return status;
55 }
56
57 status = pjmedia_vid_dev_subsys_init(&pjsua_var.cp.factory);
58 if (status != PJ_SUCCESS) {
59 PJ_PERROR(1,(THIS_FILE, status,
60 "Error creating PJMEDIA video subsystem"));
61 return status;
62 }
63
64#if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_CODEC
65 status = pjmedia_codec_ffmpeg_init(NULL, &pjsua_var.cp.factory);
66 if (status != PJ_SUCCESS) {
67 PJ_PERROR(1,(THIS_FILE, status,
68 "Error initializing ffmpeg library"));
69 return status;
70 }
71#endif
72
73 for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
74 if (pjsua_var.win[i].pool == NULL) {
75 pjsua_var.win[i].pool = pjsua_pool_create("win%p", 512, 512);
76 if (pjsua_var.win[i].pool == NULL)
77 return PJ_ENOMEM;
78 }
79 }
80
81 return PJ_SUCCESS;
82}
83
84pj_status_t pjsua_vid_subsys_start(void)
85{
86 return PJ_SUCCESS;
87}
88
89pj_status_t pjsua_vid_subsys_destroy(void)
90{
91 unsigned i;
92
93 for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
94 if (pjsua_var.win[i].pool) {
Nanang Izzuddin62053a62011-07-12 11:08:32 +000095 free_vid_win(i);
Benny Prijono9f468d12011-07-07 07:46:33 +000096 pj_pool_release(pjsua_var.win[i].pool);
97 pjsua_var.win[i].pool = NULL;
98 }
99 }
100
101 pjmedia_vid_dev_subsys_shutdown();
102
103#if PJMEDIA_HAS_FFMPEG_CODEC
104 pjmedia_codec_ffmpeg_deinit();
105#endif
106
107 return PJ_SUCCESS;
108}
109
110
111/*****************************************************************************
112 * Devices.
113 */
114
115/*
116 * Get the number of video devices installed in the system.
117 */
118PJ_DEF(unsigned) pjsua_vid_dev_count(void)
119{
120 return pjmedia_vid_dev_count();
121}
122
123/*
124 * Retrieve the video device info for the specified device index.
125 */
126PJ_DEF(pj_status_t) pjsua_vid_dev_get_info(pjmedia_vid_dev_index id,
127 pjmedia_vid_dev_info *vdi)
128{
129 return pjmedia_vid_dev_get_info(id, vdi);
130}
131
132/*
133 * Enum all video devices installed in the system.
134 */
135PJ_DEF(pj_status_t) pjsua_vid_enum_devs(pjmedia_vid_dev_info info[],
136 unsigned *count)
137{
138 unsigned i, dev_count;
139
140 dev_count = pjmedia_vid_dev_count();
141
142 if (dev_count > *count) dev_count = *count;
143
144 for (i=0; i<dev_count; ++i) {
145 pj_status_t status;
146
147 status = pjmedia_vid_dev_get_info(i, &info[i]);
148 if (status != PJ_SUCCESS)
149 return status;
150 }
151
152 *count = dev_count;
153
154 return PJ_SUCCESS;
155}
156
157
158/*****************************************************************************
159 * Codecs.
160 */
161
162/*
163 * Enum all supported video codecs in the system.
164 */
165PJ_DEF(pj_status_t) pjsua_vid_enum_codecs( pjsua_codec_info id[],
166 unsigned *p_count )
167{
168 pjmedia_vid_codec_info info[32];
169 unsigned i, j, count, prio[32];
170 pj_status_t status;
171
172 count = PJ_ARRAY_SIZE(info);
173 status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio);
174 if (status != PJ_SUCCESS) {
175 *p_count = 0;
176 return status;
177 }
178
179 for (i=0, j=0; i<count && j<*p_count; ++i) {
180 if (info[i].has_rtp_pack) {
181 pj_bzero(&id[j], sizeof(pjsua_codec_info));
182
183 pjmedia_vid_codec_info_to_id(&info[i], id[j].buf_, sizeof(id[j].buf_));
184 id[j].codec_id = pj_str(id[j].buf_);
185 id[j].priority = (pj_uint8_t) prio[i];
186
187 if (id[j].codec_id.slen < sizeof(id[j].buf_)) {
188 id[j].desc.ptr = id[j].codec_id.ptr + id[j].codec_id.slen + 1;
189 pj_strncpy(&id[j].desc, &info[i].encoding_desc,
190 sizeof(id[j].buf_) - id[j].codec_id.slen - 1);
191 }
192
193 ++j;
194 }
195 }
196
197 *p_count = j;
198
199 return PJ_SUCCESS;
200}
201
202
203/*
204 * Change video codec priority.
205 */
206PJ_DEF(pj_status_t) pjsua_vid_codec_set_priority( const pj_str_t *codec_id,
207 pj_uint8_t priority )
208{
209 const pj_str_t all = { NULL, 0 };
210
211 if (codec_id->slen==1 && *codec_id->ptr=='*')
212 codec_id = &all;
213
214 return pjmedia_vid_codec_mgr_set_codec_priority(NULL, codec_id,
215 priority);
216}
217
218
219/*
220 * Get video codec parameters.
221 */
222PJ_DEF(pj_status_t) pjsua_vid_codec_get_param(
223 const pj_str_t *codec_id,
224 pjmedia_vid_codec_param *param)
225{
226 const pj_str_t all = { NULL, 0 };
227 const pjmedia_vid_codec_info *info;
228 unsigned count = 1;
229 pj_status_t status;
230
231 if (codec_id->slen==1 && *codec_id->ptr=='*')
232 codec_id = &all;
233
234 status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
235 &count, &info, NULL);
236 if (status != PJ_SUCCESS)
237 return status;
238
239 if (count != 1)
240 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
241
242 status = pjmedia_vid_codec_mgr_get_default_param(NULL, info, param);
243 return status;
244}
245
246
247/*
248 * Set video codec parameters.
249 */
250PJ_DEF(pj_status_t) pjsua_vid_codec_set_param(
251 const pj_str_t *codec_id,
252 const pjmedia_vid_codec_param *param)
253{
254 const pjmedia_vid_codec_info *info[2];
255 unsigned count = 2;
256 pj_status_t status;
257
258 status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
259 &count, info, NULL);
260 if (status != PJ_SUCCESS)
261 return status;
262
263 /* Codec ID should be specific */
264 if (count > 1) {
265 pj_assert(!"Codec ID is not specific");
266 return PJ_ETOOMANY;
267 }
268
269 status = pjmedia_vid_codec_mgr_set_default_param(NULL, pjsua_var.pool,
270 info[0], param);
271 return status;
272}
273
274
275/*****************************************************************************
276 * Preview
277 */
278
279/*
280 * Get the preview window handle associated with the capture device, if any.
281 */
282PJ_DEF(pjsua_vid_win_id) pjsua_vid_preview_get_win(pjmedia_vid_dev_index id)
283{
284 pjsua_vid_win_id wid = PJSUA_INVALID_ID;
285 unsigned i;
286
287 PJSUA_LOCK();
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000288
289 /* Get real capture ID, if set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV */
290 if (id == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
291 pjmedia_vid_dev_info info;
292 pjmedia_vid_dev_get_info(id, &info);
293 id = info.id;
294 }
295
Benny Prijono9f468d12011-07-07 07:46:33 +0000296 for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
297 pjsua_vid_win *w = &pjsua_var.win[i];
298 if (w->type == PJSUA_WND_TYPE_PREVIEW && w->preview_cap_id == id) {
299 wid = i;
300 break;
301 }
302 }
303 PJSUA_UNLOCK();
304
305 return wid;
306}
307
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000308
309/* Allocate and initialize pjsua video window:
310 * - If the type is preview, video capture, tee, and render
311 * will be instantiated.
312 * - If the type is stream, only renderer will be created.
313 */
314static pj_status_t create_vid_win(pjsua_vid_win_type type,
315 const pjmedia_format *fmt,
316 pjmedia_vid_dev_index rend_id,
317 pjmedia_vid_dev_index cap_id,
318 pj_bool_t show,
319 pjsua_vid_win_id *id)
Benny Prijono9f468d12011-07-07 07:46:33 +0000320{
321 pjsua_vid_win_id wid = PJSUA_INVALID_ID;
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000322 pjsua_vid_win *w = NULL;
323 pjmedia_vid_port_param vp_param;
324 pjmedia_format fmt_;
325 pj_status_t status;
Benny Prijono9f468d12011-07-07 07:46:33 +0000326 unsigned i;
327
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000328 /* If type is preview, check if it exists already */
329 if (type == PJSUA_WND_TYPE_PREVIEW) {
330 wid = pjsua_vid_preview_get_win(cap_id);
331 if (wid != PJSUA_INVALID_ID) {
332 /* Yes, it exists */
333
334 /* Show window if requested */
335 if (show) {
336 pjmedia_vid_dev_stream *rdr;
Nanang Izzuddinc16590a2011-07-14 02:14:25 +0000337 pj_bool_t hide = PJ_FALSE;
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000338
339 rdr = pjmedia_vid_port_get_stream(pjsua_var.win[wid].vp_rend);
340 pj_assert(rdr);
341 status = pjmedia_vid_dev_stream_set_cap(
342 rdr,
343 PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE,
Nanang Izzuddinc16590a2011-07-14 02:14:25 +0000344 &hide);
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000345 }
346
347 /* Done */
348 *id = wid;
349 return PJ_SUCCESS;
350 }
351 }
352
353 /* Allocate window */
Benny Prijono9f468d12011-07-07 07:46:33 +0000354 for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000355 w = &pjsua_var.win[i];
Benny Prijono9f468d12011-07-07 07:46:33 +0000356 if (w->type == PJSUA_WND_TYPE_NONE) {
357 wid = i;
358 w->type = type;
359 break;
360 }
361 }
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000362 if (i == PJSUA_MAX_VID_WINS)
363 return PJ_ETOOMANY;
Benny Prijono9f468d12011-07-07 07:46:33 +0000364
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000365 /* Initialize window */
366 pjmedia_vid_port_param_default(&vp_param);
367
368 if (w->type == PJSUA_WND_TYPE_PREVIEW) {
369 status = pjmedia_vid_dev_default_param(w->pool, cap_id,
370 &vp_param.vidparam);
371 if (status != PJ_SUCCESS)
372 goto on_error;
373
374 /* Normalize capture ID, in case it was set to
375 * PJMEDIA_VID_DEFAULT_CAPTURE_DEV
376 */
377 cap_id = vp_param.vidparam.cap_id;
378
379 /* Assign preview capture device ID */
380 w->preview_cap_id = cap_id;
381
382 /* Create capture video port */
383 vp_param.active = PJ_TRUE;
384 vp_param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
385 if (fmt)
386 vp_param.vidparam.fmt = *fmt;
387
388 status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_cap);
389 if (status != PJ_SUCCESS)
390 goto on_error;
391
392 /* Update format info */
393 fmt_ = vp_param.vidparam.fmt;
394 fmt = &fmt_;
395
396 /* Create video tee */
397 status = pjmedia_vid_tee_create(w->pool, fmt, 2, &w->tee);
398 if (status != PJ_SUCCESS)
399 goto on_error;
400 }
401
402 /* Create renderer video port */
403 status = pjmedia_vid_dev_default_param(w->pool, rend_id,
404 &vp_param.vidparam);
405 if (status != PJ_SUCCESS)
406 goto on_error;
407
408 vp_param.active = (w->type == PJSUA_WND_TYPE_STREAM);
409 vp_param.vidparam.dir = PJMEDIA_DIR_RENDER;
410 vp_param.vidparam.fmt = *fmt;
411 vp_param.vidparam.disp_size = fmt->det.vid.size;
412 vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE;
413 vp_param.vidparam.window_hide = !show;
414
415 status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_rend);
416 if (status != PJ_SUCCESS)
417 goto on_error;
418
419 /* For preview window, connect capturer & renderer (via tee) */
420 if (w->type == PJSUA_WND_TYPE_PREVIEW) {
421 pjmedia_port *rend_port;
422
423 status = pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE);
424 if (status != PJ_SUCCESS)
425 goto on_error;
426
427 rend_port = pjmedia_vid_port_get_passive_port(w->vp_rend);
428 status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, rend_port);
429 if (status != PJ_SUCCESS)
430 goto on_error;
431 }
432
433 /* Done */
434 *id = wid;
435
436 return PJ_SUCCESS;
437
438on_error:
439 free_vid_win(wid);
440 return status;
Benny Prijono9f468d12011-07-07 07:46:33 +0000441}
442
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000443
Benny Prijono9f468d12011-07-07 07:46:33 +0000444static void free_vid_win(pjsua_vid_win_id wid)
445{
446 pjsua_vid_win *w = &pjsua_var.win[wid];
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000447
Benny Prijono9f468d12011-07-07 07:46:33 +0000448 if (w->vp_cap) {
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000449 pjmedia_vid_port_stop(w->vp_cap);
450 pjmedia_vid_port_disconnect(w->vp_cap);
Benny Prijono9f468d12011-07-07 07:46:33 +0000451 pjmedia_vid_port_destroy(w->vp_cap);
452 }
453 if (w->vp_rend) {
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000454 pjmedia_vid_port_stop(w->vp_rend);
Benny Prijono9f468d12011-07-07 07:46:33 +0000455 pjmedia_vid_port_destroy(w->vp_rend);
456 }
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000457 if (w->tee) {
458 pjmedia_port_destroy(w->tee);
459 }
Benny Prijono9f468d12011-07-07 07:46:33 +0000460 pjsua_vid_win_reset(wid);
461}
462
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000463
464static void inc_vid_win(pjsua_vid_win_id wid)
465{
466 pjsua_vid_win *w;
467
468 pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS);
469
470 w = &pjsua_var.win[wid];
471 pj_assert(w->type != PJSUA_WND_TYPE_NONE);
472 ++w->ref_cnt;
473}
474
475static void dec_vid_win(pjsua_vid_win_id wid)
476{
477 pjsua_vid_win *w;
478
479 pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS);
480
481 w = &pjsua_var.win[wid];
482 pj_assert(w->type != PJSUA_WND_TYPE_NONE);
483 if (--w->ref_cnt == 0)
484 free_vid_win(wid);
485}
486
487
488/* Internal function: update video channel after SDP negotiation */
489pj_status_t video_channel_update(pjsua_call_media *call_med,
490 pj_pool_t *tmp_pool,
491 const pjmedia_sdp_session *local_sdp,
492 const pjmedia_sdp_session *remote_sdp)
493{
494 pjsua_call *call = call_med->call;
495 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
496 pjmedia_vid_stream_info the_si, *si = &the_si;
497 pjmedia_port *media_port;
498 unsigned strm_idx = call_med->idx;
499 pj_status_t status;
500
501 status = pjmedia_vid_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
502 local_sdp, remote_sdp, strm_idx);
503 if (status != PJ_SUCCESS)
504 return status;
505
506 /* Check if no media is active */
507 if (si->dir == PJMEDIA_DIR_NONE) {
508 /* Call media state */
509 call_med->state = PJSUA_CALL_MEDIA_NONE;
510
511 /* Call media direction */
512 call_med->dir = PJMEDIA_DIR_NONE;
513
514 } else {
515 pjmedia_transport_info tp_info;
516
517 /* Start/restart media transport */
518 status = pjmedia_transport_media_start(call_med->tp,
519 tmp_pool, local_sdp,
520 remote_sdp, strm_idx);
521 if (status != PJ_SUCCESS)
522 return status;
523
524 call_med->tp_st = PJSUA_MED_TP_RUNNING;
525
526 /* Get remote SRTP usage policy */
527 pjmedia_transport_info_init(&tp_info);
528 pjmedia_transport_get_info(call_med->tp, &tp_info);
529 if (tp_info.specific_info_cnt > 0) {
530 unsigned i;
531 for (i = 0; i < tp_info.specific_info_cnt; ++i) {
532 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
533 {
534 pjmedia_srtp_info *srtp_info =
535 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
536
537 call_med->rem_srtp_use = srtp_info->peer_use;
538 break;
539 }
540 }
541 }
542
543 /* Optionally, application may modify other stream settings here
544 * (such as jitter buffer parameters, codec ptime, etc.)
545 */
546 si->jb_init = pjsua_var.media_cfg.jb_init;
547 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
548 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
549 si->jb_max = pjsua_var.media_cfg.jb_max;
550
551 /* Set SSRC */
552 si->ssrc = call_med->ssrc;
553
554 /* Set RTP timestamp & sequence, normally these value are intialized
555 * automatically when stream session created, but for some cases (e.g:
556 * call reinvite, call update) timestamp and sequence need to be kept
557 * contigue.
558 */
559 si->rtp_ts = call_med->rtp_tx_ts;
560 si->rtp_seq = call_med->rtp_tx_seq;
561 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
562
563#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
564 /* Enable/disable stream keep-alive and NAT hole punch. */
565 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
566#endif
567
568 /* Try to get shared format ID between the capture device and
569 * the encoder to avoid format conversion in the capture device.
570 */
571 if (si->dir & PJMEDIA_DIR_ENCODING) {
572 pjmedia_vid_dev_info dev_info;
573 pjmedia_vid_codec_info *codec_info = &si->codec_info;
574 unsigned i, j;
575
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +0000576 status = pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev,
577 &dev_info);
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000578 if (status != PJ_SUCCESS)
579 return status;
580
581 /* Find matched format ID */
582 for (i = 0; i < codec_info->dec_fmt_id_cnt; ++i) {
583 for (j = 0; j < dev_info.fmt_cnt; ++j) {
584 if (codec_info->dec_fmt_id[i] ==
585 (pjmedia_format_id)dev_info.fmt[j].id)
586 {
587 /* Apply the matched format ID to the codec */
588 si->codec_param->dec_fmt.id =
589 codec_info->dec_fmt_id[i];
590
591 /* Force outer loop to break */
592 i = codec_info->dec_fmt_id_cnt;
593 break;
594 }
595 }
596 }
597 }
598
599 /* Create session based on session info. */
600 status = pjmedia_vid_stream_create(pjsua_var.med_endpt, NULL, si,
601 call_med->tp, NULL,
602 &call_med->strm.v.stream);
603 if (status != PJ_SUCCESS)
604 return status;
605
606 /* Start stream */
607 status = pjmedia_vid_stream_start(call_med->strm.v.stream);
608 if (status != PJ_SUCCESS)
609 return status;
610
611 /* Setup decoding direction */
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +0000612 if (si->dir & PJMEDIA_DIR_DECODING)
613 {
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000614 pjsua_vid_win_id wid;
615 pjsua_vid_win *w;
616
617 status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
618 PJMEDIA_DIR_DECODING,
619 &media_port);
620 if (status != PJ_SUCCESS)
621 return status;
622
623 /* Create stream video window */
624 status = create_vid_win(PJSUA_WND_TYPE_STREAM,
625 &media_port->info.fmt,
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +0000626 call_med->strm.v.rdr_dev,
627 //acc->cfg.vid_rend_dev,
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000628 PJSUA_INVALID_ID,
629 acc->cfg.vid_in_auto_show,
630 &wid);
631 if (status != PJ_SUCCESS)
632 return status;
633
Benny Prijono1fe04ee2011-07-15 07:27:05 +0000634 w = &pjsua_var.win[wid];
635
Benny Prijonoee0ba182011-07-15 06:18:29 +0000636 /* Register to video events */
637 pjmedia_event_subscribe(
638 pjmedia_vid_port_get_event_publisher(w->vp_rend),
Benny Prijono1fe04ee2011-07-15 07:27:05 +0000639 &call_med->esub_rend);
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000640
641 /* Connect renderer to stream */
642 status = pjmedia_vid_port_connect(w->vp_rend, media_port,
643 PJ_FALSE);
644 if (status != PJ_SUCCESS)
645 return status;
646
647 /* Start renderer */
648 status = pjmedia_vid_port_start(w->vp_rend);
649 if (status != PJ_SUCCESS)
650 return status;
651
652 /* Done */
653 inc_vid_win(wid);
654 call_med->strm.v.rdr_win_id = wid;
655 }
656
657 /* Setup encoding direction */
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +0000658 if (si->dir & PJMEDIA_DIR_ENCODING && !call->local_hold)
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000659 {
660 pjsua_vid_win *w;
661 pjsua_vid_win_id wid;
662
663 status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
664 PJMEDIA_DIR_ENCODING,
665 &media_port);
666 if (status != PJ_SUCCESS)
667 return status;
668
669 /* Create preview video window */
670 status = create_vid_win(PJSUA_WND_TYPE_PREVIEW,
671 &media_port->info.fmt,
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +0000672 call_med->strm.v.rdr_dev,
673 call_med->strm.v.cap_dev,
674 //acc->cfg.vid_rend_dev,
675 //acc->cfg.vid_cap_dev,
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000676 PJ_FALSE,
677 &wid);
678 if (status != PJ_SUCCESS)
679 return status;
680
681 w = &pjsua_var.win[wid];
Benny Prijono1fe04ee2011-07-15 07:27:05 +0000682
683 pjmedia_event_subscribe(
684 pjmedia_vid_port_get_event_publisher(w->vp_cap),
685 &call_med->esub_cap);
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000686
687 /* Connect stream to capturer (via video window tee) */
688 status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port);
689 if (status != PJ_SUCCESS)
690 return status;
691
692 /* Start renderer */
693 status = pjmedia_vid_port_start(w->vp_rend);
694 if (status != PJ_SUCCESS)
695 return status;
696
697 /* Start capturer */
698 status = pjmedia_vid_port_start(w->vp_cap);
699 if (status != PJ_SUCCESS)
700 return status;
701
702 /* Done */
703 inc_vid_win(wid);
704 call_med->strm.v.cap_win_id = wid;
705 }
706
707 /* Call media direction */
708 call_med->dir = si->dir;
709
710 /* Call media state */
711 if (call->local_hold)
712 call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
713 else if (call_med->dir == PJMEDIA_DIR_DECODING)
714 call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
715 else
716 call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
717 }
718
719 /* Print info. */
720 {
721 char info[80];
722 int info_len = 0;
723 int len;
724 const char *dir;
725
726 switch (si->dir) {
727 case PJMEDIA_DIR_NONE:
728 dir = "inactive";
729 break;
730 case PJMEDIA_DIR_ENCODING:
731 dir = "sendonly";
732 break;
733 case PJMEDIA_DIR_DECODING:
734 dir = "recvonly";
735 break;
736 case PJMEDIA_DIR_ENCODING_DECODING:
737 dir = "sendrecv";
738 break;
739 default:
740 dir = "unknown";
741 break;
742 }
743 len = pj_ansi_sprintf( info+info_len,
744 ", stream #%d: %.*s (%s)", strm_idx,
745 (int)si->codec_info.encoding_name.slen,
746 si->codec_info.encoding_name.ptr,
747 dir);
748 if (len > 0)
749 info_len += len;
750 PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
751 }
752
Sauw Mingc7bc3aa2011-07-15 07:22:49 +0000753 if (!acc->cfg.vid_out_auto_transmit && call_med->strm.v.stream) {
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +0000754 status = pjmedia_vid_stream_pause(call_med->strm.v.stream,
755 PJMEDIA_DIR_ENCODING);
756 if (status != PJ_SUCCESS)
757 return status;
758 }
759
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000760 return PJ_SUCCESS;
761}
762
763
764/* Internal function to stop video stream */
765void stop_video_stream(pjsua_call_media *call_med)
766{
767 pjmedia_vid_stream *strm = call_med->strm.v.stream;
768 pjmedia_rtcp_stat stat;
769
770 pj_assert(call_med->type == PJMEDIA_TYPE_VIDEO);
771
772 if (!strm)
773 return;
774
Benny Prijono1fe04ee2011-07-15 07:27:05 +0000775 /* Unsubscribe events */
776 pjmedia_event_unsubscribe(&call_med->esub_rend);
777 pjmedia_event_unsubscribe(&call_med->esub_cap);
778
Nanang Izzuddin98085612011-07-15 07:41:02 +0000779 if (call_med->dir & PJMEDIA_DIR_ENCODING &&
780 call_med->strm.v.cap_win_id != PJSUA_INVALID_ID)
781 {
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000782 pjmedia_port *media_port;
783 pjsua_vid_win *w =
784 &pjsua_var.win[call_med->strm.v.cap_win_id];
785
786 pjmedia_vid_stream_get_port(call_med->strm.v.stream,
787 PJMEDIA_DIR_ENCODING,
788 &media_port);
789 pj_assert(media_port);
790
791 pjmedia_vid_port_stop(w->vp_cap);
792 pjmedia_vid_tee_remove_dst_port(w->tee, media_port);
793 pjmedia_vid_port_start(w->vp_cap);
794
795 dec_vid_win(call_med->strm.v.cap_win_id);
796 }
797
Nanang Izzuddin98085612011-07-15 07:41:02 +0000798 if (call_med->dir & PJMEDIA_DIR_DECODING &&
799 call_med->strm.v.rdr_win_id != PJSUA_INVALID_ID)
800 {
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000801 dec_vid_win(call_med->strm.v.rdr_win_id);
802 }
803
804 if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
805 (pjmedia_vid_stream_get_stat(strm, &stat) == PJ_SUCCESS))
806 {
807 /* Save RTP timestamp & sequence, so when media session is
808 * restarted, those values will be restored as the initial
809 * RTP timestamp & sequence of the new media session. So in
810 * the same call session, RTP timestamp and sequence are
811 * guaranteed to be contigue.
812 */
813 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
814 call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
815 call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
816 }
817
818 pjmedia_vid_stream_destroy(strm);
819 call_med->strm.v.stream = NULL;
820}
821
822
Benny Prijono9f468d12011-07-07 07:46:33 +0000823/*
824 * Start video preview window for the specified capture device.
825 */
826PJ_DEF(pj_status_t) pjsua_vid_preview_start(pjmedia_vid_dev_index id,
827 pjsua_vid_preview_param *prm)
828{
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000829 pjsua_vid_win_id wid;
830 pjsua_vid_win *w;
831 pjmedia_vid_dev_index rend_id;
Benny Prijono9f468d12011-07-07 07:46:33 +0000832 pj_status_t status;
833
834 PJSUA_LOCK();
835
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000836 if (prm) {
837 rend_id = prm->rend_id;
838 } else {
839 rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV;
Benny Prijono9f468d12011-07-07 07:46:33 +0000840 }
841
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000842 status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, NULL, rend_id, id,
843 PJ_TRUE, &wid);
844 if (status != PJ_SUCCESS) {
Benny Prijono9f468d12011-07-07 07:46:33 +0000845 PJSUA_UNLOCK();
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000846 return status;
Benny Prijono9f468d12011-07-07 07:46:33 +0000847 }
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000848
Benny Prijono9f468d12011-07-07 07:46:33 +0000849 w = &pjsua_var.win[wid];
850
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000851 /* Start capturer */
852 status = pjmedia_vid_port_start(w->vp_rend);
853 if (status != PJ_SUCCESS) {
854 PJSUA_UNLOCK();
855 return status;
Benny Prijono9f468d12011-07-07 07:46:33 +0000856 }
857
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000858 /* Start renderer */
Benny Prijono9f468d12011-07-07 07:46:33 +0000859 status = pjmedia_vid_port_start(w->vp_cap);
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000860 if (status != PJ_SUCCESS) {
861 PJSUA_UNLOCK();
862 return status;
863 }
Benny Prijono9f468d12011-07-07 07:46:33 +0000864
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000865 inc_vid_win(wid);
866
Benny Prijono9f468d12011-07-07 07:46:33 +0000867 PJSUA_UNLOCK();
868 return PJ_SUCCESS;
Benny Prijono9f468d12011-07-07 07:46:33 +0000869}
870
871/*
872 * Stop video preview.
873 */
874PJ_DEF(pj_status_t) pjsua_vid_preview_stop(pjmedia_vid_dev_index id)
875{
876 pjsua_vid_win_id wid = PJSUA_INVALID_ID;
Benny Prijono9f468d12011-07-07 07:46:33 +0000877
878 PJSUA_LOCK();
879 wid = pjsua_vid_preview_get_win(id);
880 if (wid == PJSUA_INVALID_ID) {
881 PJSUA_UNLOCK();
882 return PJ_ENOTFOUND;
883 }
884
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000885 dec_vid_win(wid);
Benny Prijono9f468d12011-07-07 07:46:33 +0000886
887 PJSUA_UNLOCK();
888
889 return PJ_SUCCESS;
890}
891
892
893/*****************************************************************************
894 * Window
895 */
896
Nanang Izzuddinf3638022011-07-14 03:47:04 +0000897
898/*
899 * Enumerates all video windows.
900 */
901PJ_DEF(pj_status_t) pjsua_vid_enum_wins( pjsua_vid_win_id wids[],
902 unsigned *count)
903{
904 unsigned i, cnt;
905
906 cnt = 0;
907
908 for (i=0; i<PJSUA_MAX_VID_WINS && cnt <*count; ++i) {
909 pjsua_vid_win *w = &pjsua_var.win[i];
910 if (w->type != PJSUA_WND_TYPE_NONE)
911 wids[cnt++] = i;
912 }
913
914 *count = cnt;
915
916 return PJ_SUCCESS;
917}
918
919
Benny Prijono9f468d12011-07-07 07:46:33 +0000920/*
921 * Get window info.
922 */
923PJ_DEF(pj_status_t) pjsua_vid_win_get_info( pjsua_vid_win_id wid,
924 pjsua_vid_win_info *wi)
925{
926 pjsua_vid_win *w;
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000927 pjmedia_vid_dev_stream *s;
928 pjmedia_vid_param vparam;
929 pj_status_t status;
Benny Prijono9f468d12011-07-07 07:46:33 +0000930
931 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && wi, PJ_EINVAL);
932
933 PJSUA_LOCK();
934 w = &pjsua_var.win[wid];
935 if (w->vp_rend == NULL) {
936 PJSUA_UNLOCK();
937 return PJ_EINVAL;
938 }
939
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000940 s = pjmedia_vid_port_get_stream(w->vp_rend);
941 if (s == NULL) {
942 PJSUA_UNLOCK();
943 return PJ_EINVAL;
944 }
945
946 status = pjmedia_vid_dev_stream_get_param(s, &vparam);
947 if (status != PJ_SUCCESS) {
948 PJSUA_UNLOCK();
949 return status;
950 }
951
952 wi->show = !vparam.window_hide;
953 wi->pos = vparam.window_pos;
954 wi->size = vparam.disp_size;
955
Benny Prijono9f468d12011-07-07 07:46:33 +0000956 PJSUA_UNLOCK();
957
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000958 return PJ_SUCCESS;
Benny Prijono9f468d12011-07-07 07:46:33 +0000959}
960
961/*
962 * Show or hide window.
963 */
964PJ_DEF(pj_status_t) pjsua_vid_win_set_show( pjsua_vid_win_id wid,
965 pj_bool_t show)
966{
967 pjsua_vid_win *w;
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000968 pjmedia_vid_dev_stream *s;
Nanang Izzuddinc16590a2011-07-14 02:14:25 +0000969 pj_bool_t hide;
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000970 pj_status_t status;
Benny Prijono9f468d12011-07-07 07:46:33 +0000971
972 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL);
973
974 PJSUA_LOCK();
975 w = &pjsua_var.win[wid];
976 if (w->vp_rend == NULL) {
977 PJSUA_UNLOCK();
978 return PJ_EINVAL;
979 }
980
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000981 s = pjmedia_vid_port_get_stream(w->vp_rend);
982 if (s == NULL) {
983 PJSUA_UNLOCK();
984 return PJ_EINVAL;
985 }
986
Nanang Izzuddinc16590a2011-07-14 02:14:25 +0000987 hide = !show;
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000988 status = pjmedia_vid_dev_stream_set_cap(s,
Nanang Izzuddinc16590a2011-07-14 02:14:25 +0000989 PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE, &hide);
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000990
Benny Prijono9f468d12011-07-07 07:46:33 +0000991 PJSUA_UNLOCK();
992
Nanang Izzuddin62053a62011-07-12 11:08:32 +0000993 return status;
Benny Prijono9f468d12011-07-07 07:46:33 +0000994}
995
996/*
997 * Set video window position.
998 */
999PJ_DEF(pj_status_t) pjsua_vid_win_set_pos( pjsua_vid_win_id wid,
1000 const pjmedia_coord *pos)
1001{
1002 pjsua_vid_win *w;
Nanang Izzuddin62053a62011-07-12 11:08:32 +00001003 pjmedia_vid_dev_stream *s;
1004 pj_status_t status;
Benny Prijono9f468d12011-07-07 07:46:33 +00001005
1006 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && pos, PJ_EINVAL);
1007
1008 PJSUA_LOCK();
1009 w = &pjsua_var.win[wid];
1010 if (w->vp_rend == NULL) {
1011 PJSUA_UNLOCK();
1012 return PJ_EINVAL;
1013 }
1014
Nanang Izzuddin62053a62011-07-12 11:08:32 +00001015 s = pjmedia_vid_port_get_stream(w->vp_rend);
1016 if (s == NULL) {
1017 PJSUA_UNLOCK();
1018 return PJ_EINVAL;
1019 }
1020
1021 status = pjmedia_vid_dev_stream_set_cap(s,
Nanang Izzuddinf3638022011-07-14 03:47:04 +00001022 PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION, pos);
Nanang Izzuddin62053a62011-07-12 11:08:32 +00001023
Benny Prijono9f468d12011-07-07 07:46:33 +00001024 PJSUA_UNLOCK();
1025
Nanang Izzuddin62053a62011-07-12 11:08:32 +00001026 return status;
Benny Prijono9f468d12011-07-07 07:46:33 +00001027}
1028
1029/*
1030 * Resize window.
1031 */
1032PJ_DEF(pj_status_t) pjsua_vid_win_set_size( pjsua_vid_win_id wid,
1033 const pjmedia_rect_size *size)
1034{
1035 pjsua_vid_win *w;
Nanang Izzuddin62053a62011-07-12 11:08:32 +00001036 pjmedia_vid_dev_stream *s;
1037 pj_status_t status;
Benny Prijono9f468d12011-07-07 07:46:33 +00001038
1039 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && size, PJ_EINVAL);
1040
1041 PJSUA_LOCK();
1042 w = &pjsua_var.win[wid];
1043 if (w->vp_rend == NULL) {
1044 PJSUA_UNLOCK();
1045 return PJ_EINVAL;
1046 }
1047
Nanang Izzuddin62053a62011-07-12 11:08:32 +00001048 s = pjmedia_vid_port_get_stream(w->vp_rend);
1049 if (s == NULL) {
1050 PJSUA_UNLOCK();
1051 return PJ_EINVAL;
1052 }
1053
1054 status = pjmedia_vid_dev_stream_set_cap(s,
Nanang Izzuddinf3638022011-07-14 03:47:04 +00001055 PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE, size);
Nanang Izzuddin62053a62011-07-12 11:08:32 +00001056
Benny Prijono9f468d12011-07-07 07:46:33 +00001057 PJSUA_UNLOCK();
1058
Nanang Izzuddin62053a62011-07-12 11:08:32 +00001059 return status;
Benny Prijono9f468d12011-07-07 07:46:33 +00001060}
1061
1062
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001063static void call_get_vid_strm_info(pjsua_call *call,
1064 int *first_active,
1065 int *first_inactive,
1066 unsigned *active_cnt,
1067 unsigned *cnt)
1068{
1069 unsigned i, var_cnt = 0;
1070
1071 if (first_active && ++var_cnt)
1072 *first_active = -1;
1073 if (first_inactive && ++var_cnt)
1074 *first_inactive = -1;
1075 if (active_cnt && ++var_cnt)
1076 *active_cnt = 0;
1077 if (cnt && ++var_cnt)
1078 *cnt = 0;
1079
1080 for (i = 0; i < call->med_cnt && var_cnt; ++i) {
1081 if (call->media[i].type == PJMEDIA_TYPE_VIDEO) {
1082 if (call->media[i].dir != PJMEDIA_DIR_NONE)
1083 {
1084 if (first_active && *first_active == -1) {
1085 *first_active = i;
1086 --var_cnt;
1087 }
1088 if (active_cnt)
1089 ++(*active_cnt);
1090 } else if (first_inactive && *first_inactive == -1) {
1091 *first_inactive = i;
1092 --var_cnt;
1093 }
1094 if (cnt)
1095 ++(*cnt);
1096 }
1097 }
1098}
1099
1100
1101/* Send SDP reoffer. */
1102static pj_status_t call_reoffer_sdp(pjsua_call_id call_id,
1103 const pjmedia_sdp_session *sdp)
1104{
1105 pjsua_call *call;
1106 pjsip_tx_data *tdata;
1107 pjsip_dialog *dlg;
1108 pj_status_t status;
1109
1110 status = acquire_call("call_reoffer_sdp()", call_id, &call, &dlg);
1111 if (status != PJ_SUCCESS)
1112 return status;
1113
1114 if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
1115 PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed"));
1116 pjsip_dlg_dec_lock(dlg);
1117 return PJSIP_ESESSIONSTATE;
1118 }
1119
1120 /* Create re-INVITE with new offer */
1121 status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata);
1122 if (status != PJ_SUCCESS) {
1123 pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
1124 pjsip_dlg_dec_lock(dlg);
1125 return status;
1126 }
1127
1128 /* Send the request */
1129 status = pjsip_inv_send_msg( call->inv, tdata);
1130 if (status != PJ_SUCCESS) {
1131 pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
1132 pjsip_dlg_dec_lock(dlg);
1133 return status;
1134 }
1135
1136 pjsip_dlg_dec_lock(dlg);
1137
1138 return PJ_SUCCESS;
1139}
1140
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001141/* Add a new video stream into a call */
1142static pj_status_t call_add_video(pjsua_call *call,
Nanang Izzuddin98085612011-07-15 07:41:02 +00001143 pjmedia_vid_dev_index cap_dev,
1144 pjmedia_dir dir)
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001145{
1146 pj_pool_t *pool = call->inv->pool_prov;
1147 pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg;
1148 pjsua_call_media *call_med;
1149 pjmedia_sdp_session *sdp;
1150 pjmedia_sdp_media *sdp_m;
1151 pjmedia_transport_info tpinfo;
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001152 unsigned active_cnt;
1153 pj_status_t status;
1154
1155 /* Verify media slot availability */
1156 if (call->med_cnt == PJSUA_MAX_CALL_MEDIA)
1157 return PJ_ETOOMANY;
1158
1159 call_get_vid_strm_info(call, NULL, NULL, &active_cnt, NULL);
1160 if (active_cnt == acc_cfg->max_video_cnt)
1161 return PJ_ETOOMANY;
1162
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001163 /* Get active local SDP */
1164 status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &sdp);
1165 if (status != PJ_SUCCESS)
1166 return status;
1167
1168 /* Initialize call media */
1169 call_med = &call->media[call->med_cnt++];
1170
1171 status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO,
1172 &acc_cfg->rtp_cfg, call->secure_level,
1173 NULL);
1174 if (status != PJ_SUCCESS)
1175 goto on_error;
1176
1177 /* Override default capture device setting */
1178 call_med->strm.v.cap_dev = cap_dev;
1179
1180 /* Init transport media */
1181 status = pjmedia_transport_media_create(call_med->tp, pool, 0,
1182 NULL, call_med->idx);
1183 if (status != PJ_SUCCESS)
1184 goto on_error;
1185
1186 call_med->tp_st = PJSUA_MED_TP_INIT;
1187
1188 /* Get transport address info */
1189 pjmedia_transport_info_init(&tpinfo);
1190 pjmedia_transport_get_info(call_med->tp, &tpinfo);
1191
1192 /* Create SDP media line */
1193 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
1194 &tpinfo.sock_info, 0, &sdp_m);
1195 if (status != PJ_SUCCESS)
1196 goto on_error;
1197
1198 sdp->media[sdp->media_count++] = sdp_m;
1199
Nanang Izzuddin98085612011-07-15 07:41:02 +00001200 /* Update media direction, if it is not 'sendrecv' */
1201 if (dir != PJMEDIA_DIR_ENCODING_DECODING) {
1202 pjmedia_sdp_attr *a;
1203
1204 /* Remove sendrecv direction attribute, if any */
1205 pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv");
1206
1207 if (dir == PJMEDIA_DIR_ENCODING)
1208 a = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
1209 else if (dir == PJMEDIA_DIR_DECODING)
1210 a = pjmedia_sdp_attr_create(pool, "recvonly", NULL);
1211 else
1212 a = pjmedia_sdp_attr_create(pool, "inactive", NULL);
1213
1214 pjmedia_sdp_media_add_attr(sdp_m, a);
1215 }
1216
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001217 /* Update SDP media line by media transport */
1218 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
1219 sdp, NULL, call_med->idx);
1220 if (status != PJ_SUCCESS)
1221 goto on_error;
1222
1223 status = call_reoffer_sdp(call->index, sdp);
1224 if (status != PJ_SUCCESS)
1225 goto on_error;
1226
1227 return PJ_SUCCESS;
1228
1229on_error:
1230 if (call_med->tp) {
1231 pjmedia_transport_close(call_med->tp);
1232 call_med->tp = call_med->tp_orig = NULL;
1233 }
1234
1235 return status;
1236}
1237
1238
Nanang Izzuddin98085612011-07-15 07:41:02 +00001239/* Modify a video stream from a call, i.e: update direction,
1240 * remove/disable.
1241 */
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001242static pj_status_t call_modify_video(pjsua_call *call,
1243 int med_idx,
Nanang Izzuddin98085612011-07-15 07:41:02 +00001244 pjmedia_dir dir,
1245 pj_bool_t remove)
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001246{
1247 pjsua_call_media *call_med;
1248 pjmedia_sdp_session *sdp;
1249 pj_status_t status;
1250
1251 /* Verify and normalize media index */
1252 if (med_idx == -1) {
1253 int first_active;
1254
1255 call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
1256 if (first_active == -1)
1257 return PJ_ENOTFOUND;
1258
1259 med_idx = first_active;
1260 }
1261
1262 call_med = &call->media[med_idx];
1263
1264 /* Verify if the stream media type is video */
1265 if (call_med->type != PJMEDIA_TYPE_VIDEO)
1266 return PJ_EINVAL;
1267
Nanang Izzuddin98085612011-07-15 07:41:02 +00001268 /* Verify if the stream dir is not changed */
1269 if ((!remove && call_med->dir == dir) ||
1270 ( remove && (call_med->tp_st == PJSUA_MED_TP_DISABLED ||
1271 call_med->tp == NULL)))
1272 {
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001273 return PJ_SUCCESS;
Nanang Izzuddin98085612011-07-15 07:41:02 +00001274 }
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001275
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001276 /* Get active local SDP */
1277 status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &sdp);
1278 if (status != PJ_SUCCESS)
1279 return status;
1280
1281 pj_assert(med_idx < (int)sdp->media_count);
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001282
Nanang Izzuddin98085612011-07-15 07:41:02 +00001283 if (!remove) {
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001284 pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg;
1285 pj_pool_t *pool = call->inv->pool_prov;
1286 pjmedia_sdp_media *sdp_m;
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001287
1288 status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO,
1289 &acc_cfg->rtp_cfg, call->secure_level,
1290 NULL);
1291 if (status != PJ_SUCCESS)
1292 goto on_error;
1293
1294 /* Init transport media */
Nanang Izzuddin98085612011-07-15 07:41:02 +00001295 if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) {
1296 status = pjmedia_transport_media_create(call_med->tp, pool, 0,
1297 NULL, call_med->idx);
1298 if (status != PJ_SUCCESS)
1299 goto on_error;
1300 }
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001301
Nanang Izzuddin98085612011-07-15 07:41:02 +00001302 sdp_m = sdp->media[med_idx];
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001303
Nanang Izzuddin98085612011-07-15 07:41:02 +00001304 /* Create new SDP media line if the stream is disabled */
1305 if (sdp->media[med_idx]->desc.port == 0) {
1306 pjmedia_transport_info tpinfo;
1307
1308 /* Get transport address info */
1309 pjmedia_transport_info_init(&tpinfo);
1310 pjmedia_transport_get_info(call_med->tp, &tpinfo);
1311
1312 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
1313 &tpinfo.sock_info, 0, &sdp_m);
1314 if (status != PJ_SUCCESS)
1315 goto on_error;
1316 }
1317
1318 {
1319 pjmedia_sdp_attr *a;
1320
1321 /* Remove any direction attributes */
1322 pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv");
1323 pjmedia_sdp_media_remove_all_attr(sdp_m, "sendonly");
1324 pjmedia_sdp_media_remove_all_attr(sdp_m, "recvonly");
1325 pjmedia_sdp_media_remove_all_attr(sdp_m, "inactive");
1326
1327 /* Update media direction */
1328 if (dir == PJMEDIA_DIR_ENCODING_DECODING)
1329 a = pjmedia_sdp_attr_create(pool, "sendrecv", NULL);
1330 else if (dir == PJMEDIA_DIR_ENCODING)
1331 a = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
1332 else if (dir == PJMEDIA_DIR_DECODING)
1333 a = pjmedia_sdp_attr_create(pool, "recvonly", NULL);
1334 else
1335 a = pjmedia_sdp_attr_create(pool, "inactive", NULL);
1336
1337 pjmedia_sdp_media_add_attr(sdp_m, a);
1338 }
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001339
1340 sdp->media[med_idx] = sdp_m;
1341
1342 /* Update SDP media line by media transport */
1343 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
1344 sdp, NULL, call_med->idx);
1345 if (status != PJ_SUCCESS)
1346 goto on_error;
1347
1348on_error:
1349 if (status != PJ_SUCCESS) {
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001350 return status;
1351 }
Nanang Izzuddin98085612011-07-15 07:41:02 +00001352
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001353 } else {
Nanang Izzuddin98085612011-07-15 07:41:02 +00001354
1355 pj_pool_t *pool = call->inv->pool_prov;
1356
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001357 /* Mark media transport to disabled */
1358 // Don't close this here, as SDP negotiation has not been
1359 // done and stream may be still active.
1360 call_med->tp_st = PJSUA_MED_TP_DISABLED;
1361
Nanang Izzuddin98085612011-07-15 07:41:02 +00001362 /* Deactivate the stream */
1363 pjmedia_sdp_media_deactivate(pool, sdp->media[med_idx]);
1364
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001365 }
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001366
1367 status = call_reoffer_sdp(call->index, sdp);
1368 if (status != PJ_SUCCESS)
1369 return status;
1370
1371 return PJ_SUCCESS;
1372}
1373
1374
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001375/* Change capture device of a video stream in a call */
1376static pj_status_t call_change_cap_dev(pjsua_call *call,
1377 int med_idx,
1378 pjmedia_vid_dev_index cap_dev)
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001379{
1380 pjsua_call_media *call_med;
1381 pjmedia_vid_dev_info info;
1382 pjsua_vid_win *w, *new_w = NULL;
1383 pjsua_vid_win_id wid, new_wid;
1384 pjmedia_port *media_port;
1385 pj_status_t status;
1386
1387 /* Verify and normalize media index */
1388 if (med_idx == -1) {
1389 int first_active;
1390
1391 call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
1392 if (first_active == -1)
1393 return PJ_ENOTFOUND;
1394
1395 med_idx = first_active;
1396 }
1397
1398 call_med = &call->media[med_idx];
1399
1400 /* Verify if the stream media type is video */
1401 if (call_med->type != PJMEDIA_TYPE_VIDEO)
1402 return PJ_EINVAL;
1403
1404 /* Verify the capture device */
1405 status = pjmedia_vid_dev_get_info(cap_dev, &info);
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001406 if (status != PJ_SUCCESS || info.dir != PJMEDIA_DIR_CAPTURE)
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001407 return PJ_EINVAL;
1408
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001409 /* The specified capture device is being used already */
1410 if (call_med->strm.v.cap_dev == cap_dev)
1411 return PJ_SUCCESS;
1412
1413 /* == Apply the new capture device == */
1414
1415 wid = call_med->strm.v.cap_win_id;
1416 w = &pjsua_var.win[wid];
1417 pj_assert(w->type == PJSUA_WND_TYPE_PREVIEW && w->vp_cap);
1418
1419 status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
1420 PJMEDIA_DIR_ENCODING, &media_port);
1421 if (status != PJ_SUCCESS)
1422 return status;
Benny Prijono1fe04ee2011-07-15 07:27:05 +00001423
1424 pjmedia_event_unsubscribe(&call_med->esub_cap);
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001425
1426 /* = Detach stream port from the old capture device = */
1427 status = pjmedia_vid_port_disconnect(w->vp_cap);
1428 if (status != PJ_SUCCESS)
1429 return status;
1430
1431 status = pjmedia_vid_tee_remove_dst_port(w->tee, media_port);
1432 if (status != PJ_SUCCESS) {
1433 /* Connect back the old capturer */
1434 pjmedia_vid_port_connect(w->vp_cap, media_port, PJ_FALSE);
1435 return status;
1436 }
1437
1438 /* = Attach stream port to the new capture device = */
1439
1440 /* Create preview video window */
1441 status = create_vid_win(PJSUA_WND_TYPE_PREVIEW,
1442 &media_port->info.fmt,
1443 call_med->strm.v.rdr_dev,
1444 cap_dev,
1445 PJ_FALSE,
1446 &new_wid);
1447 if (status != PJ_SUCCESS)
1448 goto on_error;
1449
1450 inc_vid_win(new_wid);
1451 new_w = &pjsua_var.win[new_wid];
1452
1453 /* Connect stream to capturer (via video window tee) */
1454 status = pjmedia_vid_tee_add_dst_port2(new_w->tee, 0, media_port);
1455 if (status != PJ_SUCCESS)
1456 goto on_error;
1457
1458 /* Connect capturer to tee */
1459 status = pjmedia_vid_port_connect(new_w->vp_cap, new_w->tee, PJ_FALSE);
1460 if (status != PJ_SUCCESS)
1461 return status;
1462
Benny Prijono1fe04ee2011-07-15 07:27:05 +00001463 pjmedia_event_subscribe(
1464 pjmedia_vid_port_get_event_publisher(w->vp_rend),
1465 &call_med->esub_cap);
1466
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001467 /* Start renderer */
1468 status = pjmedia_vid_port_start(new_w->vp_rend);
1469 if (status != PJ_SUCCESS)
1470 goto on_error;
1471
1472 /* Start capturer */
1473 status = pjmedia_vid_port_start(new_w->vp_cap);
1474 if (status != PJ_SUCCESS)
1475 goto on_error;
1476
1477 /* Finally */
1478 call_med->strm.v.cap_dev = cap_dev;
1479 call_med->strm.v.cap_win_id = new_wid;
1480 dec_vid_win(wid);
1481
1482 return PJ_SUCCESS;
1483
1484on_error:
1485 if (new_w) {
1486 /* Disconnect media port from the new capturer */
1487 pjmedia_vid_tee_remove_dst_port(new_w->tee, media_port);
1488 /* Release the new capturer */
1489 dec_vid_win(new_wid);
1490 }
1491
1492 /* Revert back to the old capturer */
1493 status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port);
1494 if (status != PJ_SUCCESS)
1495 return status;
1496
1497 status = pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE);
1498 if (status != PJ_SUCCESS)
1499 return status;
1500
1501 return status;
1502}
1503
1504
Nanang Izzuddin98085612011-07-15 07:41:02 +00001505/* Start/stop transmitting video stream in a call */
1506static pj_status_t call_set_tx_video(pjsua_call *call,
1507 int med_idx,
1508 pj_bool_t enable)
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001509{
1510 pjsua_call_media *call_med;
1511 pj_status_t status;
1512
1513 /* Verify and normalize media index */
1514 if (med_idx == -1) {
1515 int first_active;
1516
1517 call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
1518 if (first_active == -1)
1519 return PJ_ENOTFOUND;
1520
1521 med_idx = first_active;
1522 }
1523
1524 call_med = &call->media[med_idx];
1525
1526 /* Verify if the stream is transmitting video */
1527 if (call_med->type != PJMEDIA_TYPE_VIDEO ||
Nanang Izzuddin98085612011-07-15 07:41:02 +00001528 (enable && (call_med->dir & PJMEDIA_DIR_ENCODING) == 0))
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001529 {
1530 return PJ_EINVAL;
1531 }
1532
Nanang Izzuddin98085612011-07-15 07:41:02 +00001533 if (enable) {
1534 /* Start stream in encoding direction */
1535 status = pjmedia_vid_stream_resume(call_med->strm.v.stream,
1536 PJMEDIA_DIR_ENCODING);
1537 } else {
1538 /* Pause stream in encoding direction */
1539 status = pjmedia_vid_stream_pause( call_med->strm.v.stream,
1540 PJMEDIA_DIR_ENCODING);
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001541 }
1542
Nanang Izzuddin98085612011-07-15 07:41:02 +00001543 return status;
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001544}
1545
1546
1547/*
1548 * Start, stop, and/or manipulate video transmission for the specified call.
1549 */
1550PJ_DEF(pj_status_t) pjsua_call_set_vid_strm (
1551 pjsua_call_id call_id,
1552 pjsua_call_vid_strm_op op,
1553 const pjsua_call_vid_strm_op_param *param)
1554{
1555 pjsua_call *call;
1556 pjsua_call_vid_strm_op_param param_;
1557 pj_status_t status;
1558
1559 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
1560 PJ_EINVAL);
1561
1562 PJSUA_LOCK();
1563
1564 call = &pjsua_var.calls[call_id];
1565
1566 if (param) {
1567 param_ = *param;
1568 } else {
1569 param_.med_idx = -1;
1570 param_.cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
Nanang Izzuddin98085612011-07-15 07:41:02 +00001571 param_.dir = PJMEDIA_DIR_ENCODING_DECODING;
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001572 }
1573
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001574 /* If set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with
1575 * account default video capture device.
1576 */
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001577 if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001578 pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg;
1579 param_.cap_dev = acc_cfg->vid_cap_dev;
1580
1581 /* If the account default video capture device is
1582 * PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with
1583 * global default video capture device.
1584 */
1585 if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
1586 pjmedia_vid_dev_info info;
1587 pjmedia_vid_dev_get_info(param_.cap_dev, &info);
1588 pj_assert(info.dir == PJMEDIA_DIR_CAPTURE);
1589 param_.cap_dev = info.id;
1590 }
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001591 }
1592
1593 switch (op) {
1594 case PJSUA_CALL_VID_STRM_ADD:
Nanang Izzuddin98085612011-07-15 07:41:02 +00001595 status = call_add_video(call, param_.cap_dev, param_.dir);
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001596 break;
Nanang Izzuddin98085612011-07-15 07:41:02 +00001597 case PJSUA_CALL_VID_STRM_REMOVE:
1598 status = call_modify_video(call, param_.med_idx, PJMEDIA_DIR_NONE,
1599 PJ_TRUE);
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001600 break;
Nanang Izzuddin98085612011-07-15 07:41:02 +00001601 case PJSUA_CALL_VID_STRM_CHANGE_DIR:
1602 status = call_modify_video(call, param_.med_idx, param_.dir, PJ_FALSE);
Nanang Izzuddin7ad53c32011-07-13 13:31:08 +00001603 break;
1604 case PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV:
1605 status = call_change_cap_dev(call, param_.med_idx, param_.cap_dev);
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001606 break;
1607 case PJSUA_CALL_VID_STRM_START_TRANSMIT:
Nanang Izzuddin98085612011-07-15 07:41:02 +00001608 status = call_set_tx_video(call, param_.med_idx, PJ_TRUE);
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001609 break;
1610 case PJSUA_CALL_VID_STRM_STOP_TRANSMIT:
Nanang Izzuddin98085612011-07-15 07:41:02 +00001611 status = call_set_tx_video(call, param_.med_idx, PJ_FALSE);
Nanang Izzuddinb4d4dad2011-07-13 08:51:10 +00001612 break;
1613 default:
1614 status = PJ_EINVALIDOP;
1615 break;
1616 }
1617
1618 PJSUA_UNLOCK();
1619
1620 return status;
1621}
1622
Benny Prijono9f468d12011-07-07 07:46:33 +00001623
Nanang Izzuddinf5269a22011-07-14 04:42:18 +00001624/*
1625 * Get the media stream index of the default video stream in the call.
1626 */
1627PJ_DEF(int) pjsua_call_get_vid_stream_idx(pjsua_call_id call_id)
1628{
1629 pjsua_call *call;
1630 int first_active, first_inactive;
1631
1632 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
1633 PJ_EINVAL);
1634
1635 PJSUA_LOCK();
1636 call = &pjsua_var.calls[call_id];
1637 call_get_vid_strm_info(call, &first_active, &first_inactive, NULL, NULL);
1638 PJSUA_UNLOCK();
1639
1640 if (first_active == -1)
1641 return first_inactive;
1642
1643 return first_active;
1644}
1645
1646
Benny Prijono9f468d12011-07-07 07:46:33 +00001647#endif /* PJSUA_HAS_VIDEO */
1648