blob: 0096acabf89d4318f89f617ac653546b1f87e9ed [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $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#if defined(PJSUA_MEDIA_HAS_PJMEDIA) && PJSUA_MEDIA_HAS_PJMEDIA != 0
23
24#define THIS_FILE "pjsua_vid.c"
25
26#if PJSUA_HAS_VIDEO
27
28#define ENABLE_EVENT 1
29#define VID_TEE_MAX_PORT (PJSUA_MAX_CALLS + 1)
30
31#define PJSUA_SHOW_WINDOW 1
32#define PJSUA_HIDE_WINDOW 0
33
34
35static void free_vid_win(pjsua_vid_win_id wid);
36
37/*****************************************************************************
38 * pjsua video subsystem.
39 */
40pj_status_t pjsua_vid_subsys_init(void)
41{
42 unsigned i;
43 pj_status_t status;
44
45 PJ_LOG(4,(THIS_FILE, "Initializing video subsystem.."));
46 pj_log_push_indent();
47
48 status = pjmedia_video_format_mgr_create(pjsua_var.pool, 64, 0, NULL);
49 if (status != PJ_SUCCESS) {
50 PJ_PERROR(1,(THIS_FILE, status,
51 "Error creating PJMEDIA video format manager"));
52 goto on_error;
53 }
54
55 status = pjmedia_converter_mgr_create(pjsua_var.pool, NULL);
56 if (status != PJ_SUCCESS) {
57 PJ_PERROR(1,(THIS_FILE, status,
58 "Error creating PJMEDIA converter manager"));
59 goto on_error;
60 }
61
62 status = pjmedia_event_mgr_create(pjsua_var.pool, 0, NULL);
63 if (status != PJ_SUCCESS) {
64 PJ_PERROR(1,(THIS_FILE, status,
65 "Error creating PJMEDIA event manager"));
66 goto on_error;
67 }
68
69 status = pjmedia_vid_codec_mgr_create(pjsua_var.pool, NULL);
70 if (status != PJ_SUCCESS) {
71 PJ_PERROR(1,(THIS_FILE, status,
72 "Error creating PJMEDIA video codec manager"));
73 goto on_error;
74 }
75
76#if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_VID_CODEC
77 status = pjmedia_codec_ffmpeg_vid_init(NULL, &pjsua_var.cp.factory);
78 if (status != PJ_SUCCESS) {
79 PJ_PERROR(1,(THIS_FILE, status,
80 "Error initializing ffmpeg library"));
81 goto on_error;
82 }
83#endif
84
85 status = pjmedia_vid_dev_subsys_init(&pjsua_var.cp.factory);
86 if (status != PJ_SUCCESS) {
87 PJ_PERROR(1,(THIS_FILE, status,
88 "Error creating PJMEDIA video subsystem"));
89 goto on_error;
90 }
91
92 for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
93 if (pjsua_var.win[i].pool == NULL) {
94 pjsua_var.win[i].pool = pjsua_pool_create("win%p", 512, 512);
95 if (pjsua_var.win[i].pool == NULL) {
96 status = PJ_ENOMEM;
97 goto on_error;
98 }
99 }
100 }
101
102 pj_log_pop_indent();
103 return PJ_SUCCESS;
104
105on_error:
106 pj_log_pop_indent();
107 return status;
108}
109
110pj_status_t pjsua_vid_subsys_start(void)
111{
112 return PJ_SUCCESS;
113}
114
115pj_status_t pjsua_vid_subsys_destroy(void)
116{
117 unsigned i;
118
119 PJ_LOG(4,(THIS_FILE, "Destroying video subsystem.."));
120 pj_log_push_indent();
121
122 for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
123 if (pjsua_var.win[i].pool) {
124 free_vid_win(i);
125 pj_pool_release(pjsua_var.win[i].pool);
126 pjsua_var.win[i].pool = NULL;
127 }
128 }
129
130 pjmedia_vid_dev_subsys_shutdown();
131
132#if PJMEDIA_HAS_FFMPEG_VID_CODEC
133 pjmedia_codec_ffmpeg_vid_deinit();
134#endif
135
136 if (pjmedia_vid_codec_mgr_instance())
137 pjmedia_vid_codec_mgr_destroy(NULL);
138
139 if (pjmedia_converter_mgr_instance())
140 pjmedia_converter_mgr_destroy(NULL);
141
142 if (pjmedia_event_mgr_instance())
143 pjmedia_event_mgr_destroy(NULL);
144
145 if (pjmedia_video_format_mgr_instance())
146 pjmedia_video_format_mgr_destroy(NULL);
147
148 pj_log_pop_indent();
149 return PJ_SUCCESS;
150}
151
152PJ_DEF(const char*) pjsua_vid_win_type_name(pjsua_vid_win_type wt)
153{
154 const char *win_type_names[] = {
155 "none",
156 "preview",
157 "stream"
158 };
159
160 return (wt < PJ_ARRAY_SIZE(win_type_names)) ? win_type_names[wt] : "??";
161}
162
163PJ_DEF(void)
164pjsua_call_vid_strm_op_param_default(pjsua_call_vid_strm_op_param *param)
165{
166 pj_bzero(param, sizeof(*param));
167 param->med_idx = -1;
168 param->dir = PJMEDIA_DIR_ENCODING_DECODING;
169 param->cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
170}
171
172PJ_DEF(void) pjsua_vid_preview_param_default(pjsua_vid_preview_param *p)
173{
174 p->rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV;
175 p->show = PJ_TRUE;
176 p->wnd_flags = 0;
177}
178
179
180/*****************************************************************************
181 * Devices.
182 */
183
184/*
185 * Get the number of video devices installed in the system.
186 */
187PJ_DEF(unsigned) pjsua_vid_dev_count(void)
188{
189 return pjmedia_vid_dev_count();
190}
191
192/*
193 * Retrieve the video device info for the specified device index.
194 */
195PJ_DEF(pj_status_t) pjsua_vid_dev_get_info(pjmedia_vid_dev_index id,
196 pjmedia_vid_dev_info *vdi)
197{
198 return pjmedia_vid_dev_get_info(id, vdi);
199}
200
201/*
202 * Enum all video devices installed in the system.
203 */
204PJ_DEF(pj_status_t) pjsua_vid_enum_devs(pjmedia_vid_dev_info info[],
205 unsigned *count)
206{
207 unsigned i, dev_count;
208
209 dev_count = pjmedia_vid_dev_count();
210
211 if (dev_count > *count) dev_count = *count;
212
213 for (i=0; i<dev_count; ++i) {
214 pj_status_t status;
215
216 status = pjmedia_vid_dev_get_info(i, &info[i]);
217 if (status != PJ_SUCCESS)
218 return status;
219 }
220
221 *count = dev_count;
222
223 return PJ_SUCCESS;
224}
225
226
227/*****************************************************************************
228 * Codecs.
229 */
230
231static pj_status_t find_codecs_with_rtp_packing(
232 const pj_str_t *codec_id,
233 unsigned *count,
234 const pjmedia_vid_codec_info *p_info[])
235{
236 const pjmedia_vid_codec_info *info[32];
237 unsigned i, j, count_ = PJ_ARRAY_SIZE(info);
238 pj_status_t status;
239
240 status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
241 &count_, info, NULL);
242 if (status != PJ_SUCCESS)
243 return status;
244
245 for (i = 0, j = 0; i < count_ && j<*count; ++i) {
246 if ((info[i]->packings & PJMEDIA_VID_PACKING_PACKETS) == 0)
247 continue;
248 p_info[j++] = info[i];
249 }
250 *count = j;
251 return PJ_SUCCESS;
252}
253
254/*
255 * Enum all supported video codecs in the system.
256 */
257PJ_DEF(pj_status_t) pjsua_vid_enum_codecs( pjsua_codec_info id[],
258 unsigned *p_count )
259{
260 pjmedia_vid_codec_info info[32];
261 unsigned i, j, count, prio[32];
262 pj_status_t status;
263
264 count = PJ_ARRAY_SIZE(info);
265 status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio);
266 if (status != PJ_SUCCESS) {
267 *p_count = 0;
268 return status;
269 }
270
271 for (i=0, j=0; i<count && j<*p_count; ++i) {
272 if (info[i].packings & PJMEDIA_VID_PACKING_PACKETS) {
273 pj_bzero(&id[j], sizeof(pjsua_codec_info));
274
275 pjmedia_vid_codec_info_to_id(&info[i], id[j].buf_, sizeof(id[j].buf_));
276 id[j].codec_id = pj_str(id[j].buf_);
277 id[j].priority = (pj_uint8_t) prio[i];
278
279 if (id[j].codec_id.slen < sizeof(id[j].buf_)) {
280 id[j].desc.ptr = id[j].codec_id.ptr + id[j].codec_id.slen + 1;
281 pj_strncpy(&id[j].desc, &info[i].encoding_desc,
282 sizeof(id[j].buf_) - id[j].codec_id.slen - 1);
283 }
284
285 ++j;
286 }
287 }
288
289 *p_count = j;
290
291 return PJ_SUCCESS;
292}
293
294
295/*
296 * Change video codec priority.
297 */
298PJ_DEF(pj_status_t) pjsua_vid_codec_set_priority( const pj_str_t *codec_id,
299 pj_uint8_t priority )
300{
301 const pj_str_t all = { NULL, 0 };
302
303 if (codec_id->slen==1 && *codec_id->ptr=='*')
304 codec_id = &all;
305
306 return pjmedia_vid_codec_mgr_set_codec_priority(NULL, codec_id,
307 priority);
308}
309
310
311/*
312 * Get video codec parameters.
313 */
314PJ_DEF(pj_status_t) pjsua_vid_codec_get_param(
315 const pj_str_t *codec_id,
316 pjmedia_vid_codec_param *param)
317{
318 const pjmedia_vid_codec_info *info[2];
319 unsigned count = 2;
320 pj_status_t status;
321
322 status = find_codecs_with_rtp_packing(codec_id, &count, info);
323 if (status != PJ_SUCCESS)
324 return status;
325
326 if (count != 1)
327 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
328
329 status = pjmedia_vid_codec_mgr_get_default_param(NULL, info[0], param);
330 return status;
331}
332
333
334/*
335 * Set video codec parameters.
336 */
337PJ_DEF(pj_status_t) pjsua_vid_codec_set_param(
338 const pj_str_t *codec_id,
339 const pjmedia_vid_codec_param *param)
340{
341 const pjmedia_vid_codec_info *info[2];
342 unsigned count = 2;
343 pj_status_t status;
344
345 status = find_codecs_with_rtp_packing(codec_id, &count, info);
346 if (status != PJ_SUCCESS)
347 return status;
348
349 if (count != 1)
350 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
351
352 status = pjmedia_vid_codec_mgr_set_default_param(NULL, info[0], param);
353 return status;
354}
355
356
357/*****************************************************************************
358 * Preview
359 */
360
361static pjsua_vid_win_id vid_preview_get_win(pjmedia_vid_dev_index id,
362 pj_bool_t running_only)
363{
364 pjsua_vid_win_id wid = PJSUA_INVALID_ID;
365 unsigned i;
366
367 PJSUA_LOCK();
368
369 /* Get real capture ID, if set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV */
370 if (id == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
371 pjmedia_vid_dev_info info;
372 pjmedia_vid_dev_get_info(id, &info);
373 id = info.id;
374 }
375
376 for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
377 pjsua_vid_win *w = &pjsua_var.win[i];
378 if (w->type == PJSUA_WND_TYPE_PREVIEW && w->preview_cap_id == id) {
379 wid = i;
380 break;
381 }
382 }
383
384 if (wid != PJSUA_INVALID_ID && running_only) {
385 pjsua_vid_win *w = &pjsua_var.win[wid];
386 wid = w->preview_running ? wid : PJSUA_INVALID_ID;
387 }
388
389 PJSUA_UNLOCK();
390
391 return wid;
392}
393
394/*
395 * NOTE: internal function don't use this!!! Use vid_preview_get_win()
396 * instead. This is because this function will only return window ID
397 * if preview is currently running.
398 */
399PJ_DEF(pjsua_vid_win_id) pjsua_vid_preview_get_win(pjmedia_vid_dev_index id)
400{
401 return vid_preview_get_win(id, PJ_TRUE);
402}
403
404PJ_DEF(void) pjsua_vid_win_reset(pjsua_vid_win_id wid)
405{
406 pjsua_vid_win *w = &pjsua_var.win[wid];
407 pj_pool_t *pool = w->pool;
408
409 pj_bzero(w, sizeof(*w));
410 if (pool) pj_pool_reset(pool);
411 w->ref_cnt = 0;
412 w->pool = pool;
413 w->preview_cap_id = PJMEDIA_VID_INVALID_DEV;
414}
415
416/* Allocate and initialize pjsua video window:
417 * - If the type is preview, video capture, tee, and render
418 * will be instantiated.
419 * - If the type is stream, only renderer will be created.
420 */
421static pj_status_t create_vid_win(pjsua_vid_win_type type,
422 const pjmedia_format *fmt,
423 pjmedia_vid_dev_index rend_id,
424 pjmedia_vid_dev_index cap_id,
425 pj_bool_t show,
426 unsigned wnd_flags,
427 pjsua_vid_win_id *id)
428{
429 pj_bool_t enable_native_preview;
430 pjsua_vid_win_id wid = PJSUA_INVALID_ID;
431 pjsua_vid_win *w = NULL;
432 pjmedia_vid_port_param vp_param;
433 pjmedia_format fmt_;
434 pj_status_t status;
435 unsigned i;
436
437 enable_native_preview = pjsua_var.media_cfg.vid_preview_enable_native;
438
439 PJ_LOG(4,(THIS_FILE,
440 "Creating video window: type=%s, cap_id=%d, rend_id=%d",
441 pjsua_vid_win_type_name(type), cap_id, rend_id));
442 pj_log_push_indent();
443
444 /* If type is preview, check if it exists already */
445 if (type == PJSUA_WND_TYPE_PREVIEW) {
446 wid = vid_preview_get_win(cap_id, PJ_FALSE);
447 if (wid != PJSUA_INVALID_ID) {
448 /* Yes, it exists */
449 /* Show/hide window */
450 pjmedia_vid_dev_stream *strm;
451 pj_bool_t hide = !show;
452
453 w = &pjsua_var.win[wid];
454
455 PJ_LOG(4,(THIS_FILE,
456 "Window already exists for cap_dev=%d, returning wid=%d",
457 cap_id, wid));
458
459
460 if (w->is_native) {
461 strm = pjmedia_vid_port_get_stream(w->vp_cap);
462 } else {
463 strm = pjmedia_vid_port_get_stream(w->vp_rend);
464 }
465
466 pj_assert(strm);
467 status = pjmedia_vid_dev_stream_set_cap(
468 strm, PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE,
469 &hide);
470
471 pjmedia_vid_dev_stream_set_cap(
472 strm, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS,
473 &wnd_flags);
474
475 /* Done */
476 *id = wid;
477 pj_log_pop_indent();
478
479 return status;
480 }
481 }
482
483 /* Allocate window */
484 for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
485 w = &pjsua_var.win[i];
486 if (w->type == PJSUA_WND_TYPE_NONE) {
487 wid = i;
488 w->type = type;
489 break;
490 }
491 }
492 if (i == PJSUA_MAX_VID_WINS) {
493 pj_log_pop_indent();
494 return PJ_ETOOMANY;
495 }
496
497 /* Initialize window */
498 pjmedia_vid_port_param_default(&vp_param);
499
500 if (w->type == PJSUA_WND_TYPE_PREVIEW) {
501 pjmedia_vid_dev_info vdi;
502
503 /*
504 * Determine if the device supports native preview.
505 */
506 status = pjmedia_vid_dev_get_info(cap_id, &vdi);
507 if (status != PJ_SUCCESS)
508 goto on_error;
509
510 if (enable_native_preview &&
511 (vdi.caps & PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW))
512 {
513 /* Device supports native preview! */
514 w->is_native = PJ_TRUE;
515 }
516
517 status = pjmedia_vid_dev_default_param(w->pool, cap_id,
518 &vp_param.vidparam);
519 if (status != PJ_SUCCESS)
520 goto on_error;
521
522 if (w->is_native) {
523 vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE;
524 vp_param.vidparam.window_hide = !show;
525 vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
526 vp_param.vidparam.window_flags = wnd_flags;
527 }
528
529 /* Normalize capture ID, in case it was set to
530 * PJMEDIA_VID_DEFAULT_CAPTURE_DEV
531 */
532 cap_id = vp_param.vidparam.cap_id;
533
534 /* Assign preview capture device ID */
535 w->preview_cap_id = cap_id;
536
537 /* Create capture video port */
538 vp_param.active = PJ_TRUE;
539 vp_param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
540 if (fmt)
541 vp_param.vidparam.fmt = *fmt;
542
543 status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_cap);
544 if (status != PJ_SUCCESS)
545 goto on_error;
546
547 /* Update format info */
548 fmt_ = vp_param.vidparam.fmt;
549 fmt = &fmt_;
550
551 /* Create video tee */
552 status = pjmedia_vid_tee_create(w->pool, fmt, VID_TEE_MAX_PORT,
553 &w->tee);
554 if (status != PJ_SUCCESS)
555 goto on_error;
556
557 /* Connect capturer to the video tee */
558 status = pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE);
559 if (status != PJ_SUCCESS)
560 goto on_error;
561
562 /* If device supports native preview, enable it */
563 if (w->is_native) {
564 pjmedia_vid_dev_stream *cap_dev;
565 pj_bool_t enabled = PJ_TRUE;
566
567 cap_dev = pjmedia_vid_port_get_stream(w->vp_cap);
568 status = pjmedia_vid_dev_stream_set_cap(
569 cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW,
570 &enabled);
571 if (status != PJ_SUCCESS) {
572 PJ_PERROR(1,(THIS_FILE, status,
573 "Error activating native preview, falling back "
574 "to software preview.."));
575 w->is_native = PJ_FALSE;
576 }
577 }
578 }
579
580 /* Create renderer video port, only if it's not a native preview */
581 if (!w->is_native) {
582 status = pjmedia_vid_dev_default_param(w->pool, rend_id,
583 &vp_param.vidparam);
584 if (status != PJ_SUCCESS)
585 goto on_error;
586
587 vp_param.active = (w->type == PJSUA_WND_TYPE_STREAM);
588 vp_param.vidparam.dir = PJMEDIA_DIR_RENDER;
589 vp_param.vidparam.fmt = *fmt;
590 vp_param.vidparam.disp_size = fmt->det.vid.size;
591 vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE;
592 vp_param.vidparam.window_hide = !show;
593 vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
594 vp_param.vidparam.window_flags = wnd_flags;
595
596 status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_rend);
597 if (status != PJ_SUCCESS)
598 goto on_error;
599
600 /* For preview window, connect capturer & renderer (via tee) */
601 if (w->type == PJSUA_WND_TYPE_PREVIEW) {
602 pjmedia_port *rend_port;
603
604 rend_port = pjmedia_vid_port_get_passive_port(w->vp_rend);
605 status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, rend_port);
606 if (status != PJ_SUCCESS)
607 goto on_error;
608 }
609
610 PJ_LOG(4,(THIS_FILE,
611 "%s window id %d created for cap_dev=%d rend_dev=%d",
612 pjsua_vid_win_type_name(type), wid, cap_id, rend_id));
613 } else {
614 PJ_LOG(4,(THIS_FILE,
615 "Preview window id %d created for cap_dev %d, "
616 "using built-in preview!",
617 wid, cap_id));
618 }
619
620
621 /* Done */
622 *id = wid;
623
624 PJ_LOG(4,(THIS_FILE, "Window %d created", wid));
625 pj_log_pop_indent();
626 return PJ_SUCCESS;
627
628on_error:
629 free_vid_win(wid);
630 pj_log_pop_indent();
631 return status;
632}
633
634
635static void free_vid_win(pjsua_vid_win_id wid)
636{
637 pjsua_vid_win *w = &pjsua_var.win[wid];
638
639 PJ_LOG(4,(THIS_FILE, "Window %d: destroying..", wid));
640 pj_log_push_indent();
641
642 if (w->vp_cap) {
643 pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL,
644 w->vp_cap);
645 pjmedia_vid_port_stop(w->vp_cap);
646 pjmedia_vid_port_disconnect(w->vp_cap);
647 pjmedia_vid_port_destroy(w->vp_cap);
648 }
649 if (w->vp_rend) {
650 pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL,
651 w->vp_rend);
652 pjmedia_vid_port_stop(w->vp_rend);
653 pjmedia_vid_port_destroy(w->vp_rend);
654 }
655 if (w->tee) {
656 pjmedia_port_destroy(w->tee);
657 }
658 pjsua_vid_win_reset(wid);
659
660 pj_log_pop_indent();
661}
662
663
664static void inc_vid_win(pjsua_vid_win_id wid)
665{
666 pjsua_vid_win *w;
667
668 pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS);
669
670 w = &pjsua_var.win[wid];
671 pj_assert(w->type != PJSUA_WND_TYPE_NONE);
672 ++w->ref_cnt;
673}
674
675static void dec_vid_win(pjsua_vid_win_id wid)
676{
677 pjsua_vid_win *w;
678
679 pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS);
680
681 w = &pjsua_var.win[wid];
682 pj_assert(w->type != PJSUA_WND_TYPE_NONE);
683 if (--w->ref_cnt == 0)
684 free_vid_win(wid);
685}
686
687/* Initialize video call media */
688pj_status_t pjsua_vid_channel_init(pjsua_call_media *call_med)
689{
690 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
691
692 call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev;
693 call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev;
694 if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) {
695 pjmedia_vid_dev_info info;
696 pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info);
697 call_med->strm.v.rdr_dev = info.id;
698 }
699 if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
700 pjmedia_vid_dev_info info;
701 pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info);
702 call_med->strm.v.cap_dev = info.id;
703 }
704
705 return PJ_SUCCESS;
706}
707
708/* Internal function: update video channel after SDP negotiation.
709 * Warning: do not use temporary/flip-flop pool, e.g: inv->pool_prov,
710 * for creating stream, etc, as after SDP negotiation and when
711 * the SDP media is not changed, the stream should remain running
712 * while the temporary/flip-flop pool may be released.
713 */
714pj_status_t pjsua_vid_channel_update(pjsua_call_media *call_med,
715 pj_pool_t *tmp_pool,
716 pjmedia_vid_stream_info *si,
717 const pjmedia_sdp_session *local_sdp,
718 const pjmedia_sdp_session *remote_sdp)
719{
720 pjsua_call *call = call_med->call;
721 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
722 pjmedia_port *media_port;
723 pj_status_t status;
724
725 PJ_UNUSED_ARG(tmp_pool);
726 PJ_UNUSED_ARG(local_sdp);
727 PJ_UNUSED_ARG(remote_sdp);
728
729 PJ_LOG(4,(THIS_FILE, "Video channel update.."));
730 pj_log_push_indent();
731
732 si->rtcp_sdes_bye_disabled = pjsua_var.media_cfg.no_rtcp_sdes_bye;;
733
734 /* Check if no media is active */
735 if (si->dir != PJMEDIA_DIR_NONE) {
736 /* Optionally, application may modify other stream settings here
737 * (such as jitter buffer parameters, codec ptime, etc.)
738 */
739 si->jb_init = pjsua_var.media_cfg.jb_init;
740 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
741 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
742 si->jb_max = pjsua_var.media_cfg.jb_max;
743
744 /* Set SSRC */
745 si->ssrc = call_med->ssrc;
746
747 /* Set RTP timestamp & sequence, normally these value are intialized
748 * automatically when stream session created, but for some cases (e.g:
749 * call reinvite, call update) timestamp and sequence need to be kept
750 * contigue.
751 */
752 si->rtp_ts = call_med->rtp_tx_ts;
753 si->rtp_seq = call_med->rtp_tx_seq;
754 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
755
756 /* Set rate control config from account setting */
757 si->rc_cfg = acc->cfg.vid_stream_rc_cfg;
758
759#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
760 /* Enable/disable stream keep-alive and NAT hole punch. */
761 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
762#endif
763
764 /* Try to get shared format ID between the capture device and
765 * the encoder to avoid format conversion in the capture device.
766 */
767 if (si->dir & PJMEDIA_DIR_ENCODING) {
768 pjmedia_vid_dev_info dev_info;
769 pjmedia_vid_codec_info *codec_info = &si->codec_info;
770 unsigned i, j;
771
772 status = pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev,
773 &dev_info);
774 if (status != PJ_SUCCESS)
775 goto on_error;
776
777 /* Find matched format ID */
778 for (i = 0; i < codec_info->dec_fmt_id_cnt; ++i) {
779 for (j = 0; j < dev_info.fmt_cnt; ++j) {
780 if (codec_info->dec_fmt_id[i] ==
781 (pjmedia_format_id)dev_info.fmt[j].id)
782 {
783 /* Apply the matched format ID to the codec */
784 si->codec_param->dec_fmt.id =
785 codec_info->dec_fmt_id[i];
786
787 /* Force outer loop to break */
788 i = codec_info->dec_fmt_id_cnt;
789 break;
790 }
791 }
792 }
793 }
794
795 /* Create session based on session info. */
796 status = pjmedia_vid_stream_create(pjsua_var.med_endpt, NULL, si,
797 call_med->tp, NULL,
798 &call_med->strm.v.stream);
799 if (status != PJ_SUCCESS)
800 goto on_error;
801
802 /* Start stream */
803 status = pjmedia_vid_stream_start(call_med->strm.v.stream);
804 if (status != PJ_SUCCESS)
805 goto on_error;
806
807 if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE)
808 pjmedia_vid_stream_send_rtcp_sdes(call_med->strm.v.stream);
809
810 /* Setup decoding direction */
811 if (si->dir & PJMEDIA_DIR_DECODING)
812 {
813 pjsua_vid_win_id wid;
814 pjsua_vid_win *w;
815
816 PJ_LOG(4,(THIS_FILE, "Setting up RX.."));
817 pj_log_push_indent();
818
819 status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
820 PJMEDIA_DIR_DECODING,
821 &media_port);
822 if (status != PJ_SUCCESS) {
823 pj_log_pop_indent();
824 goto on_error;
825 }
826
827 /* Create stream video window */
828 status = create_vid_win(PJSUA_WND_TYPE_STREAM,
829 &media_port->info.fmt,
830 call_med->strm.v.rdr_dev,
831 //acc->cfg.vid_rend_dev,
832 PJSUA_INVALID_ID,
833 acc->cfg.vid_in_auto_show,
834 acc->cfg.vid_wnd_flags,
835 &wid);
836 if (status != PJ_SUCCESS) {
837 pj_log_pop_indent();
838 goto on_error;
839 }
840
841 w = &pjsua_var.win[wid];
842
843#if ENABLE_EVENT
844 /* Register to video events */
845 pjmedia_event_subscribe(NULL, &call_media_on_event,
846 call_med, w->vp_rend);
847#endif
848
849 /* Connect renderer to stream */
850 status = pjmedia_vid_port_connect(w->vp_rend, media_port,
851 PJ_FALSE);
852 if (status != PJ_SUCCESS) {
853 pj_log_pop_indent();
854 goto on_error;
855 }
856
857 /* Start renderer */
858 status = pjmedia_vid_port_start(w->vp_rend);
859 if (status != PJ_SUCCESS) {
860 pj_log_pop_indent();
861 goto on_error;
862 }
863
864 /* Done */
865 inc_vid_win(wid);
866 call_med->strm.v.rdr_win_id = wid;
867 pj_log_pop_indent();
868 }
869
870 /* Setup encoding direction */
871 if (si->dir & PJMEDIA_DIR_ENCODING && !call->local_hold)
872 {
873 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
874 pjsua_vid_win *w;
875 pjsua_vid_win_id wid;
876 pj_bool_t just_created = PJ_FALSE;
877
878 PJ_LOG(4,(THIS_FILE, "Setting up TX.."));
879 pj_log_push_indent();
880
881 status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
882 PJMEDIA_DIR_ENCODING,
883 &media_port);
884 if (status != PJ_SUCCESS) {
885 pj_log_pop_indent();
886 goto on_error;
887 }
888
889 /* Note: calling pjsua_vid_preview_get_win() even though
890 * create_vid_win() will automatically create the window
891 * if it doesn't exist, because create_vid_win() will modify
892 * existing window SHOW/HIDE value.
893 */
894 wid = vid_preview_get_win(call_med->strm.v.cap_dev, PJ_FALSE);
895 if (wid == PJSUA_INVALID_ID) {
896 /* Create preview video window */
897 status = create_vid_win(PJSUA_WND_TYPE_PREVIEW,
898 &media_port->info.fmt,
899 call_med->strm.v.rdr_dev,
900 call_med->strm.v.cap_dev,
901 //acc->cfg.vid_rend_dev,
902 //acc->cfg.vid_cap_dev,
903 PJSUA_HIDE_WINDOW,
904 acc->cfg.vid_wnd_flags,
905 &wid);
906 if (status != PJ_SUCCESS) {
907 pj_log_pop_indent();
908 return status;
909 }
910 just_created = PJ_TRUE;
911 }
912
913 w = &pjsua_var.win[wid];
914#if ENABLE_EVENT
915 pjmedia_event_subscribe(NULL, &call_media_on_event,
916 call_med, w->vp_cap);
917#endif
918
919 /* Connect stream to capturer (via video window tee) */
920 status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port);
921 if (status != PJ_SUCCESS) {
922 pj_log_pop_indent();
923 goto on_error;
924 }
925
926 /* Start capturer */
927 if (just_created) {
928 status = pjmedia_vid_port_start(w->vp_cap);
929 if (status != PJ_SUCCESS) {
930 pj_log_pop_indent();
931 goto on_error;
932 }
933 }
934
935 /* Done */
936 inc_vid_win(wid);
937 call_med->strm.v.cap_win_id = wid;
938 pj_log_pop_indent();
939 }
940
941 }
942
943 if (!acc->cfg.vid_out_auto_transmit && call_med->strm.v.stream) {
944 status = pjmedia_vid_stream_pause(call_med->strm.v.stream,
945 PJMEDIA_DIR_ENCODING);
946 if (status != PJ_SUCCESS)
947 goto on_error;
948 }
949
950 pj_log_pop_indent();
951 return PJ_SUCCESS;
952
953on_error:
954 pj_log_pop_indent();
955 return status;
956}
957
958
959/* Internal function to stop video stream */
960void pjsua_vid_stop_stream(pjsua_call_media *call_med)
961{
962 pjmedia_vid_stream *strm = call_med->strm.v.stream;
963 pjmedia_rtcp_stat stat;
964
965 pj_assert(call_med->type == PJMEDIA_TYPE_VIDEO);
966
967 if (!strm)
968 return;
969
970 PJ_LOG(4,(THIS_FILE, "Stopping video stream.."));
971 pj_log_push_indent();
972
973 pjmedia_vid_stream_send_rtcp_bye(strm);
974
975 if (call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) {
976 pjmedia_port *media_port;
977 pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.cap_win_id];
978 pj_status_t status;
979
980 /* Stop the capture before detaching stream and unsubscribing event */
981 pjmedia_vid_port_stop(w->vp_cap);
982
983 /* Disconnect video stream from capture device */
984 status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
985 PJMEDIA_DIR_ENCODING,
986 &media_port);
987 if (status == PJ_SUCCESS) {
988 pjmedia_vid_tee_remove_dst_port(w->tee, media_port);
989 }
990
991 /* Unsubscribe event */
992 pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
993 w->vp_cap);
994
995 /* Re-start capture again, if it is used by other stream */
996 if (w->ref_cnt > 1)
997 pjmedia_vid_port_start(w->vp_cap);
998
999 dec_vid_win(call_med->strm.v.cap_win_id);
1000 call_med->strm.v.cap_win_id = PJSUA_INVALID_ID;
1001 }
1002
1003 if (call_med->strm.v.rdr_win_id != PJSUA_INVALID_ID) {
1004 pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.rdr_win_id];
1005
1006 /* Stop the render before unsubscribing event */
1007 pjmedia_vid_port_stop(w->vp_rend);
1008 pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
1009 w->vp_rend);
1010
1011 dec_vid_win(call_med->strm.v.rdr_win_id);
1012 call_med->strm.v.rdr_win_id = PJSUA_INVALID_ID;
1013 }
1014
1015 if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
1016 (pjmedia_vid_stream_get_stat(strm, &stat) == PJ_SUCCESS))
1017 {
1018 /* Save RTP timestamp & sequence, so when media session is
1019 * restarted, those values will be restored as the initial
1020 * RTP timestamp & sequence of the new media session. So in
1021 * the same call session, RTP timestamp and sequence are
1022 * guaranteed to be contigue.
1023 */
1024 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
1025 call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
1026 call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
1027 }
1028
1029 pjmedia_vid_stream_destroy(strm);
1030 call_med->strm.v.stream = NULL;
1031
1032 pj_log_pop_indent();
1033}
1034
1035/*
1036 * Does it have built-in preview support.
1037 */
1038PJ_DEF(pj_bool_t) pjsua_vid_preview_has_native(pjmedia_vid_dev_index id)
1039{
1040 pjmedia_vid_dev_info vdi;
1041
1042 return (pjmedia_vid_dev_get_info(id, &vdi)==PJ_SUCCESS) ?
1043 ((vdi.caps & PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW)!=0) : PJ_FALSE;
1044}
1045
1046/*
1047 * Start video preview window for the specified capture device.
1048 */
1049PJ_DEF(pj_status_t) pjsua_vid_preview_start(pjmedia_vid_dev_index id,
1050 const pjsua_vid_preview_param *prm)
1051{
1052 pjsua_vid_win_id wid;
1053 pjsua_vid_win *w;
1054 pjmedia_vid_dev_index rend_id;
1055 pjsua_vid_preview_param default_param;
1056 pj_status_t status;
1057
1058 if (!prm) {
1059 pjsua_vid_preview_param_default(&default_param);
1060 prm = &default_param;
1061 }
1062
1063 PJ_LOG(4,(THIS_FILE, "Starting preview for cap_dev=%d, show=%d",
1064 id, prm->show));
1065 pj_log_push_indent();
1066
1067 PJSUA_LOCK();
1068
1069 rend_id = prm->rend_id;
1070
1071 status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, NULL, rend_id, id,
1072 prm->show, prm->wnd_flags, &wid);
1073 if (status != PJ_SUCCESS) {
1074 PJSUA_UNLOCK();
1075 pj_log_pop_indent();
1076 return status;
1077 }
1078
1079 w = &pjsua_var.win[wid];
1080 if (w->preview_running) {
1081 PJSUA_UNLOCK();
1082 pj_log_pop_indent();
1083 return PJ_SUCCESS;
1084 }
1085
1086 /* Start renderer, unless it's native preview */
1087 if (w->is_native && !pjmedia_vid_port_is_running(w->vp_cap)) {
1088 pjmedia_vid_dev_stream *cap_dev;
1089 pj_bool_t enabled = PJ_TRUE;
1090
1091 cap_dev = pjmedia_vid_port_get_stream(w->vp_cap);
1092 status = pjmedia_vid_dev_stream_set_cap(
1093 cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW,
1094 &enabled);
1095 if (status != PJ_SUCCESS) {
1096 PJ_PERROR(1,(THIS_FILE, status,
1097 "Error activating native preview, falling back "
1098 "to software preview.."));
1099 w->is_native = PJ_FALSE;
1100 }
1101 }
1102
1103 if (!w->is_native && !pjmedia_vid_port_is_running(w->vp_rend)) {
1104 status = pjmedia_vid_port_start(w->vp_rend);
1105 if (status != PJ_SUCCESS) {
1106 PJSUA_UNLOCK();
1107 pj_log_pop_indent();
1108 return status;
1109 }
1110 }
1111
1112 /* Start capturer */
1113 if (!pjmedia_vid_port_is_running(w->vp_cap)) {
1114 status = pjmedia_vid_port_start(w->vp_cap);
1115 if (status != PJ_SUCCESS) {
1116 PJSUA_UNLOCK();
1117 pj_log_pop_indent();
1118 return status;
1119 }
1120 }
1121
1122 inc_vid_win(wid);
1123 w->preview_running = PJ_TRUE;
1124
1125 PJSUA_UNLOCK();
1126 pj_log_pop_indent();
1127 return PJ_SUCCESS;
1128}
1129
1130/*
1131 * Stop video preview.
1132 */
1133PJ_DEF(pj_status_t) pjsua_vid_preview_stop(pjmedia_vid_dev_index id)
1134{
1135 pjsua_vid_win_id wid = PJSUA_INVALID_ID;
1136 pjsua_vid_win *w;
1137 pj_status_t status;
1138
1139 PJSUA_LOCK();
1140 wid = pjsua_vid_preview_get_win(id);
1141 if (wid == PJSUA_INVALID_ID) {
1142 PJSUA_UNLOCK();
1143 pj_log_pop_indent();
1144 return PJ_ENOTFOUND;
1145 }
1146
1147 PJ_LOG(4,(THIS_FILE, "Stopping preview for cap_dev=%d", id));
1148 pj_log_push_indent();
1149
1150 w = &pjsua_var.win[wid];
1151 if (w->preview_running) {
1152 if (w->is_native) {
1153 pjmedia_vid_dev_stream *cap_dev;
1154 pj_bool_t enabled = PJ_FALSE;
1155
1156 cap_dev = pjmedia_vid_port_get_stream(w->vp_cap);
1157 status = pjmedia_vid_dev_stream_set_cap(
1158 cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW,
1159 &enabled);
1160 } else {
1161 status = pjmedia_vid_port_stop(w->vp_rend);
1162 }
1163
1164 if (status != PJ_SUCCESS) {
1165 PJ_PERROR(1,(THIS_FILE, status, "Error stopping %spreview",
1166 (w->is_native ? "native " : "")));
1167 PJSUA_UNLOCK();
1168 pj_log_pop_indent();
1169 return status;
1170 }
1171
1172 dec_vid_win(wid);
1173 w->preview_running = PJ_FALSE;
1174 }
1175
1176 PJSUA_UNLOCK();
1177 pj_log_pop_indent();
1178
1179 return PJ_SUCCESS;
1180}
1181
1182
1183/*****************************************************************************
1184 * Window
1185 */
1186
1187
1188/*
1189 * Enumerates all video windows.
1190 */
1191PJ_DEF(pj_status_t) pjsua_vid_enum_wins( pjsua_vid_win_id wids[],
1192 unsigned *count)
1193{
1194 unsigned i, cnt;
1195
1196 cnt = 0;
1197
1198 for (i=0; i<PJSUA_MAX_VID_WINS && cnt <*count; ++i) {
1199 pjsua_vid_win *w = &pjsua_var.win[i];
1200 if (w->type != PJSUA_WND_TYPE_NONE)
1201 wids[cnt++] = i;
1202 }
1203
1204 *count = cnt;
1205
1206 return PJ_SUCCESS;
1207}
1208
1209
1210/*
1211 * Get window info.
1212 */
1213PJ_DEF(pj_status_t) pjsua_vid_win_get_info( pjsua_vid_win_id wid,
1214 pjsua_vid_win_info *wi)
1215{
1216 pjsua_vid_win *w;
1217 pjmedia_vid_dev_stream *s;
1218 pjmedia_vid_dev_param vparam;
1219 pj_status_t status;
1220
1221 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && wi, PJ_EINVAL);
1222
1223 pj_bzero(wi, sizeof(*wi));
1224
1225 PJSUA_LOCK();
1226 w = &pjsua_var.win[wid];
1227
1228 wi->is_native = w->is_native;
1229
1230 if (w->is_native) {
1231 pjmedia_vid_dev_stream *cap_strm;
1232 pjmedia_vid_dev_cap cap = PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
1233
1234 cap_strm = pjmedia_vid_port_get_stream(w->vp_cap);
1235 if (!cap_strm) {
1236 status = PJ_EINVAL;
1237 } else {
1238 status = pjmedia_vid_dev_stream_get_cap(cap_strm, cap, &wi->hwnd);
1239 }
1240
1241 PJSUA_UNLOCK();
1242 return status;
1243 }
1244
1245 if (w->vp_rend == NULL) {
1246 PJSUA_UNLOCK();
1247 return PJ_EINVAL;
1248 }
1249
1250 s = pjmedia_vid_port_get_stream(w->vp_rend);
1251 if (s == NULL) {
1252 PJSUA_UNLOCK();
1253 return PJ_EINVAL;
1254 }
1255
1256 status = pjmedia_vid_dev_stream_get_param(s, &vparam);
1257 if (status != PJ_SUCCESS) {
1258 PJSUA_UNLOCK();
1259 return status;
1260 }
1261
1262 wi->rdr_dev = vparam.rend_id;
1263 wi->hwnd = vparam.window;
1264 wi->show = !vparam.window_hide;
1265 wi->pos = vparam.window_pos;
1266 wi->size = vparam.disp_size;
1267
1268 PJSUA_UNLOCK();
1269
1270 return PJ_SUCCESS;
1271}
1272
1273/*
1274 * Show or hide window.
1275 */
1276PJ_DEF(pj_status_t) pjsua_vid_win_set_show( pjsua_vid_win_id wid,
1277 pj_bool_t show)
1278{
1279 pjsua_vid_win *w;
1280 pjmedia_vid_dev_stream *s;
1281 pj_bool_t hide;
1282 pj_status_t status;
1283
1284 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL);
1285
1286 PJSUA_LOCK();
1287 w = &pjsua_var.win[wid];
1288 if (w->vp_rend == NULL) {
1289 /* Native window */
1290 PJSUA_UNLOCK();
1291 return PJ_EINVAL;
1292 }
1293
1294 s = pjmedia_vid_port_get_stream(w->vp_rend);
1295 if (s == NULL) {
1296 PJSUA_UNLOCK();
1297 return PJ_EINVAL;
1298 }
1299
1300 /* Make sure that renderer gets started before shown up */
1301 if (show && !pjmedia_vid_port_is_running(w->vp_rend))
1302 status = pjmedia_vid_port_start(w->vp_rend);
1303
1304 hide = !show;
1305 status = pjmedia_vid_dev_stream_set_cap(s,
1306 PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE, &hide);
1307
1308 PJSUA_UNLOCK();
1309
1310 return status;
1311}
1312
1313/*
1314 * Set video window position.
1315 */
1316PJ_DEF(pj_status_t) pjsua_vid_win_set_pos( pjsua_vid_win_id wid,
1317 const pjmedia_coord *pos)
1318{
1319 pjsua_vid_win *w;
1320 pjmedia_vid_dev_stream *s;
1321 pj_status_t status;
1322
1323 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && pos, PJ_EINVAL);
1324
1325 PJSUA_LOCK();
1326 w = &pjsua_var.win[wid];
1327 if (w->vp_rend == NULL) {
1328 /* Native window */
1329 PJSUA_UNLOCK();
1330 return PJ_EINVAL;
1331 }
1332
1333 s = pjmedia_vid_port_get_stream(w->vp_rend);
1334 if (s == NULL) {
1335 PJSUA_UNLOCK();
1336 return PJ_EINVAL;
1337 }
1338
1339 status = pjmedia_vid_dev_stream_set_cap(s,
1340 PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION, pos);
1341
1342 PJSUA_UNLOCK();
1343
1344 return status;
1345}
1346
1347/*
1348 * Resize window.
1349 */
1350PJ_DEF(pj_status_t) pjsua_vid_win_set_size( pjsua_vid_win_id wid,
1351 const pjmedia_rect_size *size)
1352{
1353 pjsua_vid_win *w;
1354 pjmedia_vid_dev_stream *s;
1355 pj_status_t status;
1356
1357 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && size, PJ_EINVAL);
1358
1359 PJSUA_LOCK();
1360 w = &pjsua_var.win[wid];
1361 if (w->vp_rend == NULL) {
1362 /* Native window */
1363 PJSUA_UNLOCK();
1364 return PJ_EINVAL;
1365 }
1366
1367 s = pjmedia_vid_port_get_stream(w->vp_rend);
1368 if (s == NULL) {
1369 PJSUA_UNLOCK();
1370 return PJ_EINVAL;
1371 }
1372
1373 status = pjmedia_vid_dev_stream_set_cap(s,
1374 PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE, size);
1375
1376 PJSUA_UNLOCK();
1377
1378 return status;
1379}
1380
1381/*
1382 * Set video orientation.
1383 */
1384PJ_DEF(pj_status_t) pjsua_vid_win_rotate( pjsua_vid_win_id wid,
1385 int angle)
1386{
1387 pjsua_vid_win *w;
1388 pjmedia_vid_dev_stream *s;
1389 pjmedia_orient orient;
1390 pj_status_t status;
1391
1392 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL);
1393 PJ_ASSERT_RETURN((angle % 90) == 0, PJ_EINVAL);
1394
1395 /* Normalize angle, so it must be 0, 90, 180, or 270. */
1396 angle %= 360;
1397 if (angle < 0)
1398 angle += 360;
1399
1400 /* Convert angle to pjmedia_orient */
1401 switch(angle) {
1402 case 0:
1403 /* No rotation */
1404 return PJ_SUCCESS;
1405 case 90:
1406 orient = PJMEDIA_ORIENT_ROTATE_90DEG;
1407 break;
1408 case 180:
1409 orient = PJMEDIA_ORIENT_ROTATE_180DEG;
1410 break;
1411 case 270:
1412 orient = PJMEDIA_ORIENT_ROTATE_270DEG;
1413 break;
1414 default:
1415 pj_assert(!"Angle must have been validated");
1416 return PJ_EBUG;
1417 }
1418
1419 PJSUA_LOCK();
1420 w = &pjsua_var.win[wid];
1421 if (w->vp_rend == NULL) {
1422 /* Native window */
1423 PJSUA_UNLOCK();
1424 return PJ_EINVAL;
1425 }
1426
1427 s = pjmedia_vid_port_get_stream(w->vp_rend);
1428 if (s == NULL) {
1429 PJSUA_UNLOCK();
1430 return PJ_EINVAL;
1431 }
1432
1433 status = pjmedia_vid_dev_stream_set_cap(s,
1434 PJMEDIA_VID_DEV_CAP_ORIENTATION, &orient);
1435
1436 PJSUA_UNLOCK();
1437
1438 return status;
1439}
1440
1441
1442static void call_get_vid_strm_info(pjsua_call *call,
1443 int *first_active,
1444 int *first_inactive,
1445 unsigned *active_cnt,
1446 unsigned *cnt)
1447{
1448 unsigned i, var_cnt = 0;
1449
1450 if (first_active && ++var_cnt)
1451 *first_active = -1;
1452 if (first_inactive && ++var_cnt)
1453 *first_inactive = -1;
1454 if (active_cnt && ++var_cnt)
1455 *active_cnt = 0;
1456 if (cnt && ++var_cnt)
1457 *cnt = 0;
1458
1459 for (i = 0; i < call->med_cnt && var_cnt; ++i) {
1460 if (call->media[i].type == PJMEDIA_TYPE_VIDEO) {
1461 if (call->media[i].dir != PJMEDIA_DIR_NONE)
1462 {
1463 if (first_active && *first_active == -1) {
1464 *first_active = i;
1465 --var_cnt;
1466 }
1467 if (active_cnt)
1468 ++(*active_cnt);
1469 } else if (first_inactive && *first_inactive == -1) {
1470 *first_inactive = i;
1471 --var_cnt;
1472 }
1473 if (cnt)
1474 ++(*cnt);
1475 }
1476 }
1477}
1478
1479
1480/* Send SDP reoffer. */
1481static pj_status_t call_reoffer_sdp(pjsua_call_id call_id,
1482 const pjmedia_sdp_session *sdp)
1483{
1484 pjsua_call *call;
1485 pjsip_tx_data *tdata;
1486 pjsip_dialog *dlg;
1487 pj_status_t status;
1488
1489 status = acquire_call("call_reoffer_sdp()", call_id, &call, &dlg);
1490 if (status != PJ_SUCCESS)
1491 return status;
1492
1493 if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
1494 PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed"));
1495 pjsip_dlg_dec_lock(dlg);
1496 return PJSIP_ESESSIONSTATE;
1497 }
1498
1499 /* Create re-INVITE with new offer */
1500 status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata);
1501 if (status != PJ_SUCCESS) {
1502 pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
1503 pjsip_dlg_dec_lock(dlg);
1504 return status;
1505 }
1506
1507 /* Send the request */
1508 status = pjsip_inv_send_msg( call->inv, tdata);
1509 if (status != PJ_SUCCESS) {
1510 pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
1511 pjsip_dlg_dec_lock(dlg);
1512 return status;
1513 }
1514
1515 pjsip_dlg_dec_lock(dlg);
1516
1517 return PJ_SUCCESS;
1518}
1519
1520/* Add a new video stream into a call */
1521static pj_status_t call_add_video(pjsua_call *call,
1522 pjmedia_vid_dev_index cap_dev,
1523 pjmedia_dir dir)
1524{
1525 pj_pool_t *pool = call->inv->pool_prov;
1526 pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg;
1527 pjsua_call_media *call_med;
1528 const pjmedia_sdp_session *current_sdp;
1529 pjmedia_sdp_session *sdp;
1530 pjmedia_sdp_media *sdp_m;
1531 pjmedia_transport_info tpinfo;
1532 pj_status_t status;
1533
1534 /* Verify media slot availability */
1535 if (call->med_cnt == PJSUA_MAX_CALL_MEDIA)
1536 return PJ_ETOOMANY;
1537
1538 /* Get active local SDP and clone it */
1539 status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &current_sdp);
1540 if (status != PJ_SUCCESS)
1541 return status;
1542
1543 sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, current_sdp);
1544
1545 /* Clean up provisional media before using it */
1546 pjsua_media_prov_clean_up(call->index);
1547
1548 /* Update provisional media from call media */
1549 call->med_prov_cnt = call->med_cnt;
1550 pj_memcpy(call->media_prov, call->media,
1551 sizeof(call->media[0]) * call->med_cnt);
1552
1553 /* Initialize call media */
1554 call_med = &call->media_prov[call->med_prov_cnt++];
1555 status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO,
1556 &acc_cfg->rtp_cfg, call->secure_level,
1557 NULL, PJ_FALSE, NULL);
1558 if (status != PJ_SUCCESS)
1559 goto on_error;
1560
1561 /* Override default capture device setting */
1562 call_med->strm.v.cap_dev = cap_dev;
1563
1564 /* Init transport media */
1565 status = pjmedia_transport_media_create(call_med->tp, pool, 0,
1566 NULL, call_med->idx);
1567 if (status != PJ_SUCCESS)
1568 goto on_error;
1569
1570 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
1571
1572 /* Get transport address info */
1573 pjmedia_transport_info_init(&tpinfo);
1574 pjmedia_transport_get_info(call_med->tp, &tpinfo);
1575
1576 /* Create SDP media line */
1577 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
1578 &tpinfo.sock_info, 0, &sdp_m);
1579 if (status != PJ_SUCCESS)
1580 goto on_error;
1581
1582 sdp->media[sdp->media_count++] = sdp_m;
1583
1584 /* Update media direction, if it is not 'sendrecv' */
1585 if (dir != PJMEDIA_DIR_ENCODING_DECODING) {
1586 pjmedia_sdp_attr *a;
1587
1588 /* Remove sendrecv direction attribute, if any */
1589 pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv");
1590
1591 if (dir == PJMEDIA_DIR_ENCODING)
1592 a = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
1593 else if (dir == PJMEDIA_DIR_DECODING)
1594 a = pjmedia_sdp_attr_create(pool, "recvonly", NULL);
1595 else
1596 a = pjmedia_sdp_attr_create(pool, "inactive", NULL);
1597
1598 pjmedia_sdp_media_add_attr(sdp_m, a);
1599 }
1600
1601 /* Update SDP media line by media transport */
1602 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
1603 sdp, NULL, call_med->idx);
1604 if (status != PJ_SUCCESS)
1605 goto on_error;
1606
1607 status = call_reoffer_sdp(call->index, sdp);
1608 if (status != PJ_SUCCESS)
1609 goto on_error;
1610
1611 call->opt.vid_cnt++;
1612
1613 return PJ_SUCCESS;
1614
1615on_error:
1616 if (call_med->tp) {
1617 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL);
1618 pjmedia_transport_close(call_med->tp);
1619 call_med->tp = call_med->tp_orig = NULL;
1620 }
1621
1622 return status;
1623}
1624
1625
1626/* Modify a video stream from a call, i.e: update direction,
1627 * remove/disable.
1628 */
1629static pj_status_t call_modify_video(pjsua_call *call,
1630 int med_idx,
1631 pjmedia_dir dir,
1632 pj_bool_t remove)
1633{
1634 pjsua_call_media *call_med;
1635 const pjmedia_sdp_session *current_sdp;
1636 pjmedia_sdp_session *sdp;
1637 pj_status_t status;
1638
1639 /* Verify and normalize media index */
1640 if (med_idx == -1) {
1641 int first_active;
1642
1643 call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
1644 if (first_active == -1)
1645 return PJ_ENOTFOUND;
1646
1647 med_idx = first_active;
1648 }
1649
1650 /* Clean up provisional media before using it */
1651 pjsua_media_prov_clean_up(call->index);
1652
1653 /* Update provisional media from call media */
1654 call->med_prov_cnt = call->med_cnt;
1655 pj_memcpy(call->media_prov, call->media,
1656 sizeof(call->media[0]) * call->med_cnt);
1657
1658 call_med = &call->media_prov[med_idx];
1659
1660 /* Verify if the stream media type is video */
1661 if (call_med->type != PJMEDIA_TYPE_VIDEO)
1662 return PJ_EINVAL;
1663
1664 /* Verify if the stream dir is not changed */
1665 if ((!remove && call_med->dir == dir) ||
1666 ( remove && (call_med->tp_st == PJSUA_MED_TP_DISABLED ||
1667 call_med->tp == NULL)))
1668 {
1669 return PJ_SUCCESS;
1670 }
1671
1672 /* Get active local SDP and clone it */
1673 status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &current_sdp);
1674 if (status != PJ_SUCCESS)
1675 return status;
1676
1677 sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, current_sdp);
1678
1679 pj_assert(med_idx < (int)sdp->media_count);
1680
1681 if (!remove) {
1682 pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg;
1683 pj_pool_t *pool = call->inv->pool_prov;
1684 pjmedia_sdp_media *sdp_m;
1685
1686 /* Enabling video */
1687 if (call_med->dir == PJMEDIA_DIR_NONE) {
1688 unsigned i, vid_cnt = 0;
1689
1690 /* Check if vid_cnt in call option needs to be increased */
1691 for (i = 0; i < call->med_cnt; ++i) {
1692 if (call->media[i].type == PJMEDIA_TYPE_VIDEO &&
1693 call->media[i].dir != PJMEDIA_DIR_NONE)
1694 {
1695 ++vid_cnt;
1696 }
1697 }
1698 if (call->opt.vid_cnt <= vid_cnt)
1699 call->opt.vid_cnt++;
1700 }
1701
1702 status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO,
1703 &acc_cfg->rtp_cfg, call->secure_level,
1704 NULL, PJ_FALSE, NULL);
1705 if (status != PJ_SUCCESS)
1706 goto on_error;
1707
1708 /* Init transport media */
1709 if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) {
1710 status = pjmedia_transport_media_create(call_med->tp, pool, 0,
1711 NULL, call_med->idx);
1712 if (status != PJ_SUCCESS)
1713 goto on_error;
1714 }
1715
1716 sdp_m = sdp->media[med_idx];
1717
1718 /* Create new SDP media line if the stream is disabled */
1719 if (sdp->media[med_idx]->desc.port == 0) {
1720 pjmedia_transport_info tpinfo;
1721
1722 /* Get transport address info */
1723 pjmedia_transport_info_init(&tpinfo);
1724 pjmedia_transport_get_info(call_med->tp, &tpinfo);
1725
1726 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
1727 &tpinfo.sock_info, 0, &sdp_m);
1728 if (status != PJ_SUCCESS)
1729 goto on_error;
1730 }
1731
1732 {
1733 pjmedia_sdp_attr *a;
1734
1735 /* Remove any direction attributes */
1736 pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv");
1737 pjmedia_sdp_media_remove_all_attr(sdp_m, "sendonly");
1738 pjmedia_sdp_media_remove_all_attr(sdp_m, "recvonly");
1739 pjmedia_sdp_media_remove_all_attr(sdp_m, "inactive");
1740
1741 /* Update media direction */
1742 if (dir == PJMEDIA_DIR_ENCODING_DECODING)
1743 a = pjmedia_sdp_attr_create(pool, "sendrecv", NULL);
1744 else if (dir == PJMEDIA_DIR_ENCODING)
1745 a = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
1746 else if (dir == PJMEDIA_DIR_DECODING)
1747 a = pjmedia_sdp_attr_create(pool, "recvonly", NULL);
1748 else
1749 a = pjmedia_sdp_attr_create(pool, "inactive", NULL);
1750
1751 pjmedia_sdp_media_add_attr(sdp_m, a);
1752 }
1753
1754 sdp->media[med_idx] = sdp_m;
1755
1756 if (call_med->dir == PJMEDIA_DIR_NONE) {
1757 /* Update SDP media line by media transport */
1758 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
1759 sdp, NULL, call_med->idx);
1760 if (status != PJ_SUCCESS)
1761 goto on_error;
1762 }
1763
1764on_error:
1765 if (status != PJ_SUCCESS) {
1766 pjsua_media_prov_clean_up(call->index);
1767 return status;
1768 }
1769
1770 } else {
1771
1772 pj_pool_t *pool = call->inv->pool_prov;
1773
1774 /* Mark media transport to disabled */
1775 // Don't close this here, as SDP negotiation has not been
1776 // done and stream may be still active.
1777 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);
1778
1779 /* Deactivate the stream */
1780 pjmedia_sdp_media_deactivate(pool, sdp->media[med_idx]);
1781
1782 call->opt.vid_cnt--;
1783 }
1784
1785 status = call_reoffer_sdp(call->index, sdp);
1786 if (status != PJ_SUCCESS)
1787 return status;
1788
1789 return PJ_SUCCESS;
1790}
1791
1792
1793/* Change capture device of a video stream in a call */
1794static pj_status_t call_change_cap_dev(pjsua_call *call,
1795 int med_idx,
1796 pjmedia_vid_dev_index cap_dev)
1797{
1798 pjsua_call_media *call_med;
1799 pjmedia_vid_dev_stream *old_dev;
1800 pjmedia_vid_dev_switch_param switch_prm;
1801 pjmedia_vid_dev_info info;
1802 pjsua_vid_win *w, *new_w = NULL;
1803 pjsua_vid_win_id wid, new_wid;
1804 pjmedia_port *media_port;
1805 pj_status_t status;
1806
1807 /* Verify and normalize media index */
1808 if (med_idx == -1) {
1809 int first_active;
1810
1811 call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
1812 if (first_active == -1)
1813 return PJ_ENOTFOUND;
1814
1815 med_idx = first_active;
1816 }
1817
1818 call_med = &call->media[med_idx];
1819
1820 /* Verify if the stream media type is video */
1821 if (call_med->type != PJMEDIA_TYPE_VIDEO)
1822 return PJ_EINVAL;
1823
1824 /* Verify the capture device */
1825 status = pjmedia_vid_dev_get_info(cap_dev, &info);
1826 if (status != PJ_SUCCESS || info.dir != PJMEDIA_DIR_CAPTURE)
1827 return PJ_EINVAL;
1828
1829 /* The specified capture device is being used already */
1830 if (call_med->strm.v.cap_dev == cap_dev)
1831 return PJ_SUCCESS;
1832
1833 /* == Apply the new capture device == */
1834
1835 wid = call_med->strm.v.cap_win_id;
1836 w = &pjsua_var.win[wid];
1837 pj_assert(w->type == PJSUA_WND_TYPE_PREVIEW && w->vp_cap);
1838
1839 /* If the old device supports fast switching, then that's excellent! */
1840 old_dev = pjmedia_vid_port_get_stream(w->vp_cap);
1841 pjmedia_vid_dev_switch_param_default(&switch_prm);
1842 switch_prm.target_id = cap_dev;
1843 status = pjmedia_vid_dev_stream_set_cap(old_dev,
1844 PJMEDIA_VID_DEV_CAP_SWITCH,
1845 &switch_prm);
1846 if (status == PJ_SUCCESS) {
1847 w->preview_cap_id = cap_dev;
1848 call_med->strm.v.cap_dev = cap_dev;
1849 return PJ_SUCCESS;
1850 }
1851
1852 /* No it doesn't support fast switching. Do slow switching then.. */
1853 status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
1854 PJMEDIA_DIR_ENCODING, &media_port);
1855 if (status != PJ_SUCCESS)
1856 return status;
1857
1858 pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
1859 w->vp_cap);
1860
1861 /* temporarily disconnect while we operate on the tee. */
1862 pjmedia_vid_port_disconnect(w->vp_cap);
1863
1864 /* = Detach stream port from the old capture device's tee = */
1865 status = pjmedia_vid_tee_remove_dst_port(w->tee, media_port);
1866 if (status != PJ_SUCCESS) {
1867 /* Something wrong, assume that media_port has been removed
1868 * and continue.
1869 */
1870 PJ_PERROR(4,(THIS_FILE, status,
1871 "Warning: call %d: unable to remove video from tee",
1872 call->index));
1873 }
1874
1875 /* Reconnect again immediately. We're done with w->tee */
1876 pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE);
1877
1878 /* = Attach stream port to the new capture device = */
1879
1880 /* Note: calling pjsua_vid_preview_get_win() even though
1881 * create_vid_win() will automatically create the window
1882 * if it doesn't exist, because create_vid_win() will modify
1883 * existing window SHOW/HIDE value.
1884 */
1885 new_wid = vid_preview_get_win(cap_dev, PJ_FALSE);
1886 if (new_wid == PJSUA_INVALID_ID) {
1887 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
1888
1889 /* Create preview video window */
1890 status = create_vid_win(PJSUA_WND_TYPE_PREVIEW,
1891 &media_port->info.fmt,
1892 call_med->strm.v.rdr_dev,
1893 cap_dev,
1894 PJSUA_HIDE_WINDOW,
1895 acc->cfg.vid_wnd_flags,
1896 &new_wid);
1897 if (status != PJ_SUCCESS)
1898 goto on_error;
1899 }
1900
1901 inc_vid_win(new_wid);
1902 new_w = &pjsua_var.win[new_wid];
1903
1904 /* Connect stream to capturer (via video window tee) */
1905 status = pjmedia_vid_tee_add_dst_port2(new_w->tee, 0, media_port);
1906 if (status != PJ_SUCCESS)
1907 goto on_error;
1908
1909 if (w->vp_rend) {
1910 /* Start renderer */
1911 status = pjmedia_vid_port_start(new_w->vp_rend);
1912 if (status != PJ_SUCCESS)
1913 goto on_error;
1914 }
1915
1916#if ENABLE_EVENT
1917 pjmedia_event_subscribe(NULL, &call_media_on_event,
1918 call_med, new_w->vp_cap);
1919#endif
1920
1921 /* Start capturer */
1922 if (!pjmedia_vid_port_is_running(new_w->vp_cap)) {
1923 status = pjmedia_vid_port_start(new_w->vp_cap);
1924 if (status != PJ_SUCCESS)
1925 goto on_error;
1926 }
1927
1928 /* Finally */
1929 call_med->strm.v.cap_dev = cap_dev;
1930 call_med->strm.v.cap_win_id = new_wid;
1931 dec_vid_win(wid);
1932
1933 return PJ_SUCCESS;
1934
1935on_error:
1936 PJ_PERROR(4,(THIS_FILE, status,
1937 "Call %d: error changing capture device to %d",
1938 call->index, cap_dev));
1939
1940 if (new_w) {
1941 /* Unsubscribe, just in case */
1942 pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
1943 new_w->vp_cap);
1944 /* Disconnect media port from the new capturer */
1945 pjmedia_vid_tee_remove_dst_port(new_w->tee, media_port);
1946 /* Release the new capturer */
1947 dec_vid_win(new_wid);
1948 }
1949
1950 /* Revert back to the old capturer */
1951 pjmedia_vid_port_disconnect(w->vp_cap);
1952 status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port);
1953 pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE);
1954 if (status != PJ_SUCCESS)
1955 return status;
1956
1957#if ENABLE_EVENT
1958 /* Resubscribe */
1959 pjmedia_event_subscribe(NULL, &call_media_on_event,
1960 call_med, w->vp_cap);
1961#endif
1962
1963 return status;
1964}
1965
1966
1967/* Start/stop transmitting video stream in a call */
1968static pj_status_t call_set_tx_video(pjsua_call *call,
1969 int med_idx,
1970 pj_bool_t enable)
1971{
1972 pjsua_call_media *call_med;
1973 pj_status_t status;
1974
1975 /* Verify and normalize media index */
1976 if (med_idx == -1) {
1977 int first_active;
1978
1979 call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
1980 if (first_active == -1)
1981 return PJ_ENOTFOUND;
1982
1983 med_idx = first_active;
1984 }
1985
1986 call_med = &call->media[med_idx];
1987
1988 /* Verify if the stream is transmitting video */
1989 if (call_med->type != PJMEDIA_TYPE_VIDEO ||
1990 (enable && (call_med->dir & PJMEDIA_DIR_ENCODING) == 0))
1991 {
1992 return PJ_EINVAL;
1993 }
1994
1995 if (enable) {
1996 /* Start stream in encoding direction */
1997 status = pjmedia_vid_stream_resume(call_med->strm.v.stream,
1998 PJMEDIA_DIR_ENCODING);
1999 } else {
2000 /* Pause stream in encoding direction */
2001 status = pjmedia_vid_stream_pause( call_med->strm.v.stream,
2002 PJMEDIA_DIR_ENCODING);
2003 }
2004
2005 return status;
2006}
2007
2008
2009static pj_status_t call_send_vid_keyframe(pjsua_call *call,
2010 int med_idx)
2011{
2012 pjsua_call_media *call_med;
2013
2014 /* Verify and normalize media index */
2015 if (med_idx == -1) {
2016 int first_active;
2017
2018 call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
2019 if (first_active == -1)
2020 return PJ_ENOTFOUND;
2021
2022 med_idx = first_active;
2023 }
2024
2025 call_med = &call->media[med_idx];
2026
2027 /* Verify media type and stream instance. */
2028 if (call_med->type != PJMEDIA_TYPE_VIDEO || !call_med->strm.v.stream)
2029 return PJ_EINVAL;
2030
2031 return pjmedia_vid_stream_send_keyframe(call_med->strm.v.stream);
2032}
2033
2034
2035/*
2036 * Start, stop, and/or manipulate video transmission for the specified call.
2037 */
2038PJ_DEF(pj_status_t) pjsua_call_set_vid_strm (
2039 pjsua_call_id call_id,
2040 pjsua_call_vid_strm_op op,
2041 const pjsua_call_vid_strm_op_param *param)
2042{
2043 pjsua_call *call;
2044 pjsip_dialog *dlg = NULL;
2045 pjsua_call_vid_strm_op_param param_;
2046 pj_status_t status;
2047
2048 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
2049 PJ_EINVAL);
2050 PJ_ASSERT_RETURN(op != PJSUA_CALL_VID_STRM_NO_OP, PJ_EINVAL);
2051
2052 PJ_LOG(4,(THIS_FILE, "Call %d: set video stream, op=%d",
2053 call_id, op));
2054 pj_log_push_indent();
2055
2056 status = acquire_call("pjsua_call_set_vid_strm()", call_id, &call, &dlg);
2057 if (status != PJ_SUCCESS)
2058 goto on_return;
2059
2060 if (param) {
2061 param_ = *param;
2062 } else {
2063 pjsua_call_vid_strm_op_param_default(&param_);
2064 }
2065
2066 /* If set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with
2067 * account default video capture device.
2068 */
2069 if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
2070 pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg;
2071 param_.cap_dev = acc_cfg->vid_cap_dev;
2072
2073 /* If the account default video capture device is
2074 * PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with
2075 * global default video capture device.
2076 */
2077 if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
2078 pjmedia_vid_dev_info info;
2079 pjmedia_vid_dev_get_info(param_.cap_dev, &info);
2080 pj_assert(info.dir == PJMEDIA_DIR_CAPTURE);
2081 param_.cap_dev = info.id;
2082 }
2083 }
2084
2085 switch (op) {
2086 case PJSUA_CALL_VID_STRM_ADD:
2087 status = call_add_video(call, param_.cap_dev, param_.dir);
2088 break;
2089 case PJSUA_CALL_VID_STRM_REMOVE:
2090 status = call_modify_video(call, param_.med_idx, PJMEDIA_DIR_NONE,
2091 PJ_TRUE);
2092 break;
2093 case PJSUA_CALL_VID_STRM_CHANGE_DIR:
2094 status = call_modify_video(call, param_.med_idx, param_.dir, PJ_FALSE);
2095 break;
2096 case PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV:
2097 status = call_change_cap_dev(call, param_.med_idx, param_.cap_dev);
2098 break;
2099 case PJSUA_CALL_VID_STRM_START_TRANSMIT:
2100 status = call_set_tx_video(call, param_.med_idx, PJ_TRUE);
2101 break;
2102 case PJSUA_CALL_VID_STRM_STOP_TRANSMIT:
2103 status = call_set_tx_video(call, param_.med_idx, PJ_FALSE);
2104 break;
2105 case PJSUA_CALL_VID_STRM_SEND_KEYFRAME:
2106 status = call_send_vid_keyframe(call, param_.med_idx);
2107 break;
2108 default:
2109 status = PJ_EINVALIDOP;
2110 break;
2111 }
2112
2113on_return:
2114 if (dlg) pjsip_dlg_dec_lock(dlg);
2115 pj_log_pop_indent();
2116 return status;
2117}
2118
2119
2120/*
2121 * Get the media stream index of the default video stream in the call.
2122 */
2123PJ_DEF(int) pjsua_call_get_vid_stream_idx(pjsua_call_id call_id)
2124{
2125 pjsua_call *call;
2126 int first_active, first_inactive;
2127
2128 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
2129 PJ_EINVAL);
2130
2131 PJSUA_LOCK();
2132 call = &pjsua_var.calls[call_id];
2133 call_get_vid_strm_info(call, &first_active, &first_inactive, NULL, NULL);
2134 PJSUA_UNLOCK();
2135
2136 if (first_active == -1)
2137 return first_inactive;
2138
2139 return first_active;
2140}
2141
2142
2143/*
2144 * Determine if video stream for the specified call is currently running
2145 * for the specified direction.
2146 */
2147PJ_DEF(pj_bool_t) pjsua_call_vid_stream_is_running( pjsua_call_id call_id,
2148 int med_idx,
2149 pjmedia_dir dir)
2150{
2151 pjsua_call *call;
2152 pjsua_call_media *call_med;
2153
2154 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
2155 PJ_EINVAL);
2156
2157 /* Verify and normalize media index */
2158 if (med_idx == -1) {
2159 med_idx = pjsua_call_get_vid_stream_idx(call_id);
2160 }
2161
2162 call = &pjsua_var.calls[call_id];
2163 PJ_ASSERT_RETURN(med_idx >= 0 && med_idx < (int)call->med_cnt, PJ_EINVAL);
2164
2165 call_med = &call->media[med_idx];
2166
2167 /* Verify if the stream is transmitting video */
2168 if (call_med->type != PJMEDIA_TYPE_VIDEO || (call_med->dir & dir) == 0 ||
2169 !call_med->strm.v.stream)
2170 {
2171 return PJ_FALSE;
2172 }
2173
2174 return pjmedia_vid_stream_is_running(call_med->strm.v.stream, dir);
2175}
2176
2177#endif /* PJSUA_HAS_VIDEO */
2178
2179#endif /* PJSUA_MEDIA_HAS_PJMEDIA */