1 /* $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
30 #define PJSUA_SHOW_WINDOW 1
31 #define PJSUA_HIDE_WINDOW 0
32
33
34 static pjsua_vid_win_id vid_preview_get_win(pjmedia_vid_dev_index id,
35 pj_bool_t running_only);
36 static void free_vid_win(pjsua_vid_win_id wid);
37
38 /*****************************************************************************
39 * pjsua video subsystem.
40 */
pjsua_vid_subsys_init(void)41 pj_status_t pjsua_vid_subsys_init(void)
42 {
43 unsigned i;
44 pj_status_t status;
45
46 PJ_LOG(4,(THIS_FILE, "Initializing video subsystem.."));
47 pj_log_push_indent();
48
49 status = pjmedia_video_format_mgr_create(pjsua_var.pool, 64, 0, NULL);
50 if (status != PJ_SUCCESS) {
51 pjsua_perror(THIS_FILE, "Error creating PJMEDIA video format manager",
52 status);
53 goto on_error;
54 }
55
56 status = pjmedia_converter_mgr_create(pjsua_var.pool, NULL);
57 if (status != PJ_SUCCESS) {
58 pjsua_perror(THIS_FILE, "Error creating PJMEDIA converter manager",
59 status);
60 goto on_error;
61 }
62
63 status = pjmedia_vid_codec_mgr_create(pjsua_var.pool, NULL);
64 if (status != PJ_SUCCESS) {
65 pjsua_perror(THIS_FILE, "Error creating PJMEDIA video codec manager",
66 status);
67 goto on_error;
68 }
69
70 status = pjmedia_vid_conf_create(pjsua_var.pool, NULL,
71 &pjsua_var.vid_conf);
72 if (status != PJ_SUCCESS) {
73 pjsua_perror(THIS_FILE,
74 "Error creating PJMEDIA video conference bridge",
75 status);
76 goto on_error;
77 }
78
79 #if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_VID_TOOLBOX_CODEC
80 status = pjmedia_codec_vid_toolbox_init(NULL, &pjsua_var.cp.factory);
81 if (status != PJ_SUCCESS) {
82 pjsua_perror(THIS_FILE, "Error initializing Video Toolbox codec",
83 status);
84 goto on_error;
85 }
86 #endif
87
88 #if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_ANDROID_MEDIACODEC
89 status = pjmedia_codec_and_media_vid_init(NULL, &pjsua_var.cp.factory);
90 if (status != PJ_SUCCESS) {
91 pjsua_perror(THIS_FILE, "Error initializing AMediaCodec library",
92 status);
93 goto on_error;
94 }
95 #endif
96
97 #if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_OPENH264_CODEC
98 status = pjmedia_codec_openh264_vid_init(NULL, &pjsua_var.cp.factory);
99 if (status != PJ_SUCCESS) {
100 pjsua_perror(THIS_FILE, "Error initializing OpenH264 library",
101 status);
102 goto on_error;
103 }
104 #endif
105
106 #if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_VID_CODEC
107 status = pjmedia_codec_ffmpeg_vid_init(NULL, &pjsua_var.cp.factory);
108 if (status != PJ_SUCCESS) {
109 pjsua_perror(THIS_FILE, "Error initializing ffmpeg library", status);
110 goto on_error;
111 }
112 #endif
113
114 #if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_VPX_CODEC
115 status = pjmedia_codec_vpx_vid_init(NULL, &pjsua_var.cp.factory);
116 if (status != PJ_SUCCESS) {
117 pjsua_perror(THIS_FILE, "Error initializing VPX library",
118 status);
119 goto on_error;
120 }
121 #endif
122
123 status = pjmedia_vid_dev_subsys_init(&pjsua_var.cp.factory);
124 if (status != PJ_SUCCESS) {
125 pjsua_perror(THIS_FILE, "Error creating PJMEDIA video subsystem",
126 status);
127 goto on_error;
128 }
129
130 for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
131 if (pjsua_var.win[i].pool == NULL) {
132 pjsua_var.win[i].pool = pjsua_pool_create("win%p", 512, 512);
133 if (pjsua_var.win[i].pool == NULL) {
134 status = PJ_ENOMEM;
135 goto on_error;
136 }
137 }
138 }
139
140 pj_log_pop_indent();
141 return PJ_SUCCESS;
142
143 on_error:
144 pj_log_pop_indent();
145 return status;
146 }
147
pjsua_vid_subsys_start(void)148 pj_status_t pjsua_vid_subsys_start(void)
149 {
150 return PJ_SUCCESS;
151 }
152
pjsua_vid_subsys_destroy(void)153 pj_status_t pjsua_vid_subsys_destroy(void)
154 {
155 unsigned i;
156
157 PJ_LOG(4,(THIS_FILE, "Destroying video subsystem.."));
158 pj_log_push_indent();
159
160 for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
161 if (pjsua_var.win[i].pool) {
162 free_vid_win(i);
163 pj_pool_release(pjsua_var.win[i].pool);
164 pjsua_var.win[i].pool = NULL;
165 }
166 }
167
168 if (pjsua_var.vid_conf) {
169 pjmedia_vid_conf_destroy(pjsua_var.vid_conf);
170 pjsua_var.vid_conf = NULL;
171 }
172
173 pjmedia_vid_dev_subsys_shutdown();
174
175 #if PJMEDIA_HAS_FFMPEG_VID_CODEC
176 pjmedia_codec_ffmpeg_vid_deinit();
177 #endif
178
179 #if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_VID_TOOLBOX_CODEC
180 pjmedia_codec_vid_toolbox_deinit();
181 #endif
182
183 #if defined(PJMEDIA_HAS_ANDROID_MEDIACODEC) && \
184 PJMEDIA_HAS_ANDROID_MEDIACODEC != 0
185 pjmedia_codec_and_media_vid_deinit();
186 #endif
187
188 #if defined(PJMEDIA_HAS_OPENH264_CODEC) && PJMEDIA_HAS_OPENH264_CODEC != 0
189 pjmedia_codec_openh264_vid_deinit();
190 #endif
191
192 #if defined(PJMEDIA_HAS_VPX_CODEC) && PJMEDIA_HAS_VPX_CODEC != 0
193 pjmedia_codec_vpx_vid_deinit();
194 #endif
195
196 if (pjmedia_vid_codec_mgr_instance())
197 pjmedia_vid_codec_mgr_destroy(NULL);
198
199 if (pjmedia_converter_mgr_instance())
200 pjmedia_converter_mgr_destroy(NULL);
201
202 if (pjmedia_video_format_mgr_instance())
203 pjmedia_video_format_mgr_destroy(NULL);
204
205 pj_log_pop_indent();
206 return PJ_SUCCESS;
207 }
208
pjsua_vid_win_type_name(pjsua_vid_win_type wt)209 PJ_DEF(const char*) pjsua_vid_win_type_name(pjsua_vid_win_type wt)
210 {
211 const char *win_type_names[] = {
212 "none",
213 "preview",
214 "stream"
215 };
216
217 return (wt < PJ_ARRAY_SIZE(win_type_names)) ? win_type_names[wt] : "??";
218 }
219
220 PJ_DEF(void)
pjsua_call_vid_strm_op_param_default(pjsua_call_vid_strm_op_param * param)221 pjsua_call_vid_strm_op_param_default(pjsua_call_vid_strm_op_param *param)
222 {
223 pj_bzero(param, sizeof(*param));
224 param->med_idx = -1;
225 param->dir = PJMEDIA_DIR_ENCODING_DECODING;
226 param->cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
227 }
228
pjsua_vid_preview_param_default(pjsua_vid_preview_param * p)229 PJ_DEF(void) pjsua_vid_preview_param_default(pjsua_vid_preview_param *p)
230 {
231 p->rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV;
232 p->show = PJ_TRUE;
233 p->wnd_flags = 0;
234 pj_bzero(&p->format, sizeof(p->format));
235 pj_bzero(&p->wnd, sizeof(p->wnd));
236 }
237
238
239 /*****************************************************************************
240 * Devices.
241 */
242
243 /*
244 * Get the number of video devices installed in the system.
245 */
pjsua_vid_dev_count(void)246 PJ_DEF(unsigned) pjsua_vid_dev_count(void)
247 {
248 return pjmedia_vid_dev_count();
249 }
250
251 /*
252 * Retrieve the video device info for the specified device index.
253 */
pjsua_vid_dev_get_info(pjmedia_vid_dev_index id,pjmedia_vid_dev_info * vdi)254 PJ_DEF(pj_status_t) pjsua_vid_dev_get_info(pjmedia_vid_dev_index id,
255 pjmedia_vid_dev_info *vdi)
256 {
257 return pjmedia_vid_dev_get_info(id, vdi);
258 }
259
260 /*
261 * Check whether the video device is currently active.
262 */
pjsua_vid_dev_is_active(pjmedia_vid_dev_index id)263 PJ_DEF(pj_bool_t) pjsua_vid_dev_is_active(pjmedia_vid_dev_index id)
264 {
265 pjsua_vid_win_id wid = vid_preview_get_win(id, PJ_FALSE);
266
267 return (wid != PJSUA_INVALID_ID? PJ_TRUE: PJ_FALSE);
268 }
269
270 /*
271 * Set the capability of the video device.
272 */
pjsua_vid_dev_set_setting(pjmedia_vid_dev_index id,pjmedia_vid_dev_cap cap,const void * pval,pj_bool_t keep)273 PJ_DEF(pj_status_t) pjsua_vid_dev_set_setting( pjmedia_vid_dev_index id,
274 pjmedia_vid_dev_cap cap,
275 const void *pval,
276 pj_bool_t keep)
277 {
278 pj_status_t status = PJ_SUCCESS;
279 pjsua_vid_win_id wid;
280
281 PJSUA_LOCK();
282 wid = vid_preview_get_win(id, PJ_FALSE);
283 if (wid != PJSUA_INVALID_ID) {
284 pjsua_vid_win *w;
285 pjmedia_vid_dev_stream *cap_dev;
286
287 w = &pjsua_var.win[wid];
288 cap_dev = pjmedia_vid_port_get_stream(w->vp_cap);
289
290 status = pjmedia_vid_dev_stream_set_cap(cap_dev, cap, pval);
291 if (status != PJ_SUCCESS) {
292 PJSUA_UNLOCK();
293 return status;
294 }
295 } else {
296 status = PJ_ENOTFOUND;
297 }
298 PJSUA_UNLOCK();
299
300 if (keep) {
301 pjmedia_vid_dev_info info;
302
303 status = pjmedia_vid_dev_get_info(id, &info);
304 if (status != PJ_SUCCESS || (info.dir & PJMEDIA_DIR_CAPTURE) == 0)
305 return status;
306
307 /* Get real capture ID, if set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV */
308 id = info.id;
309 status = pjmedia_vid_dev_param_set_cap(&pjsua_var.vid_param[id],
310 cap, pval);
311 if (status == PJ_SUCCESS) {
312 pjsua_var.vid_caps[id] |= cap;
313 }
314 }
315
316 return status;
317 }
318
319 /*
320 * Get the value of the video device capability.
321 */
pjsua_vid_dev_get_setting(pjmedia_vid_dev_index id,pjmedia_vid_dev_cap cap,void * pval)322 PJ_DEF(pj_status_t) pjsua_vid_dev_get_setting( pjmedia_vid_dev_index id,
323 pjmedia_vid_dev_cap cap,
324 void *pval)
325 {
326 pj_status_t status = PJ_SUCCESS;
327 pjsua_vid_win_id wid;
328
329 PJSUA_LOCK();
330 wid = vid_preview_get_win(id, PJ_FALSE);
331 if (wid != PJSUA_INVALID_ID) {
332 pjsua_vid_win *w;
333 pjmedia_vid_dev_stream *cap_dev;
334
335 w = &pjsua_var.win[wid];
336 cap_dev = pjmedia_vid_port_get_stream(w->vp_cap);
337
338 status = pjmedia_vid_dev_stream_get_cap(cap_dev, cap, pval);
339
340 PJSUA_UNLOCK();
341 } else {
342 pjmedia_vid_dev_info info;
343
344 PJSUA_UNLOCK();
345
346 status = pjmedia_vid_dev_get_info(id, &info);
347 if (status != PJ_SUCCESS)
348 return status;
349
350 /* Get real device ID, if set to default device */
351 id = info.id;
352
353 if ((pjsua_var.vid_caps[id] & cap) != 0) {
354 status = pjmedia_vid_dev_param_get_cap(&pjsua_var.vid_param[id],
355 cap, pval);
356 } else {
357 status = PJ_ENOTFOUND;
358 }
359 }
360
361 return status;
362 }
363
364 /*
365 * Enum all video devices installed in the system.
366 */
pjsua_vid_enum_devs(pjmedia_vid_dev_info info[],unsigned * count)367 PJ_DEF(pj_status_t) pjsua_vid_enum_devs(pjmedia_vid_dev_info info[],
368 unsigned *count)
369 {
370 unsigned i, dev_count;
371
372 dev_count = pjmedia_vid_dev_count();
373
374 if (dev_count > *count) dev_count = *count;
375
376 for (i=0; i<dev_count; ++i) {
377 pj_status_t status;
378
379 status = pjmedia_vid_dev_get_info(i, &info[i]);
380 if (status != PJ_SUCCESS)
381 return status;
382 }
383
384 *count = dev_count;
385
386 return PJ_SUCCESS;
387 }
388
389
390 /*****************************************************************************
391 * Codecs.
392 */
393
find_codecs_with_rtp_packing(const pj_str_t * codec_id,unsigned * count,const pjmedia_vid_codec_info * p_info[])394 static pj_status_t find_codecs_with_rtp_packing(
395 const pj_str_t *codec_id,
396 unsigned *count,
397 const pjmedia_vid_codec_info *p_info[])
398 {
399 const pjmedia_vid_codec_info *info[32];
400 unsigned i, j, count_ = PJ_ARRAY_SIZE(info);
401 pj_status_t status;
402
403 status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
404 &count_, info, NULL);
405 if (status != PJ_SUCCESS)
406 return status;
407
408 for (i = 0, j = 0; i < count_ && j<*count; ++i) {
409 if ((info[i]->packings & PJMEDIA_VID_PACKING_PACKETS) == 0)
410 continue;
411 p_info[j++] = info[i];
412 }
413 *count = j;
414 return PJ_SUCCESS;
415 }
416
417 /*
418 * Enum all supported video codecs in the system.
419 */
pjsua_vid_enum_codecs(pjsua_codec_info id[],unsigned * p_count)420 PJ_DEF(pj_status_t) pjsua_vid_enum_codecs( pjsua_codec_info id[],
421 unsigned *p_count )
422 {
423 pjmedia_vid_codec_info info[32];
424 unsigned i, j, count, prio[32];
425 pj_status_t status;
426
427 count = PJ_ARRAY_SIZE(info);
428 status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio);
429 if (status != PJ_SUCCESS) {
430 *p_count = 0;
431 return status;
432 }
433
434 for (i=0, j=0; i<count && j<*p_count; ++i) {
435 if (info[i].packings & PJMEDIA_VID_PACKING_PACKETS) {
436 pj_bzero(&id[j], sizeof(pjsua_codec_info));
437
438 pjmedia_vid_codec_info_to_id(&info[i], id[j].buf_, sizeof(id[j].buf_));
439 id[j].codec_id = pj_str(id[j].buf_);
440 id[j].priority = (pj_uint8_t) prio[i];
441
442 if (id[j].codec_id.slen < sizeof(id[j].buf_)) {
443 id[j].desc.ptr = id[j].codec_id.ptr + id[j].codec_id.slen + 1;
444 pj_strncpy(&id[j].desc, &info[i].encoding_desc,
445 sizeof(id[j].buf_) - id[j].codec_id.slen - 1);
446 }
447
448 ++j;
449 }
450 }
451
452 *p_count = j;
453
454 return PJ_SUCCESS;
455 }
456
457
458 /*
459 * Change video codec priority.
460 */
pjsua_vid_codec_set_priority(const pj_str_t * codec_id,pj_uint8_t priority)461 PJ_DEF(pj_status_t) pjsua_vid_codec_set_priority( const pj_str_t *codec_id,
462 pj_uint8_t priority )
463 {
464 const pj_str_t all = { NULL, 0 };
465
466 if (codec_id->slen==1 && *codec_id->ptr=='*')
467 codec_id = &all;
468
469 return pjmedia_vid_codec_mgr_set_codec_priority(NULL, codec_id,
470 priority);
471 }
472
473
474 /*
475 * Get video codec parameters.
476 */
pjsua_vid_codec_get_param(const pj_str_t * codec_id,pjmedia_vid_codec_param * param)477 PJ_DEF(pj_status_t) pjsua_vid_codec_get_param(
478 const pj_str_t *codec_id,
479 pjmedia_vid_codec_param *param)
480 {
481 const pjmedia_vid_codec_info *info[2];
482 unsigned count = 2;
483 pj_status_t status;
484
485 status = find_codecs_with_rtp_packing(codec_id, &count, info);
486 if (status != PJ_SUCCESS)
487 return status;
488
489 if (count != 1)
490 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
491
492 status = pjmedia_vid_codec_mgr_get_default_param(NULL, info[0], param);
493 return status;
494 }
495
496
497 /*
498 * Set video codec parameters.
499 */
pjsua_vid_codec_set_param(const pj_str_t * codec_id,const pjmedia_vid_codec_param * param)500 PJ_DEF(pj_status_t) pjsua_vid_codec_set_param(
501 const pj_str_t *codec_id,
502 const pjmedia_vid_codec_param *param)
503 {
504 const pjmedia_vid_codec_info *info[2];
505 unsigned count = 2;
506 pj_status_t status;
507
508 status = find_codecs_with_rtp_packing(codec_id, &count, info);
509 if (status != PJ_SUCCESS)
510 return status;
511
512 if (count != 1)
513 return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
514
515 status = pjmedia_vid_codec_mgr_set_default_param(NULL, info[0], param);
516 return status;
517 }
518
519
520 /*****************************************************************************
521 * Preview
522 */
523
vid_preview_get_win(pjmedia_vid_dev_index id,pj_bool_t running_only)524 static pjsua_vid_win_id vid_preview_get_win(pjmedia_vid_dev_index id,
525 pj_bool_t running_only)
526 {
527 pjsua_vid_win_id wid = PJSUA_INVALID_ID;
528 unsigned i;
529
530 PJSUA_LOCK();
531
532 /* Get real capture ID, if set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV */
533 if (id == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
534 pjmedia_vid_dev_info info;
535 pjmedia_vid_dev_get_info(id, &info);
536 id = info.id;
537 }
538
539 for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
540 pjsua_vid_win *w = &pjsua_var.win[i];
541 if (w->type == PJSUA_WND_TYPE_PREVIEW && w->preview_cap_id == id) {
542 wid = i;
543 break;
544 }
545 }
546
547 if (wid != PJSUA_INVALID_ID && running_only) {
548 pjsua_vid_win *w = &pjsua_var.win[wid];
549 wid = w->preview_running ? wid : PJSUA_INVALID_ID;
550 }
551
552 PJSUA_UNLOCK();
553
554 return wid;
555 }
556
557 /*
558 * NOTE: internal function don't use this!!! Use vid_preview_get_win()
559 * instead. This is because this function will only return window ID
560 * if preview is currently running.
561 */
pjsua_vid_preview_get_win(pjmedia_vid_dev_index id)562 PJ_DEF(pjsua_vid_win_id) pjsua_vid_preview_get_win(pjmedia_vid_dev_index id)
563 {
564 return vid_preview_get_win(id, PJ_TRUE);
565 }
566
567 /*
568 * Get video conference slot ID of the specified capture device.
569 */
pjsua_vid_preview_get_vid_conf_port(pjmedia_vid_dev_index id)570 PJ_DEF(pjsua_conf_port_id) pjsua_vid_preview_get_vid_conf_port(
571 pjmedia_vid_dev_index id)
572 {
573 pjsua_vid_win_id wid;
574 pjsua_vid_win *w;
575 pjsua_conf_port_id conf_id = PJSUA_INVALID_ID;
576
577 PJSUA_LOCK();
578 wid = vid_preview_get_win(id, PJ_TRUE);
579 if (wid != PJSUA_INVALID_ID) {
580 w = &pjsua_var.win[wid];
581 conf_id = w->cap_slot;
582 }
583 PJSUA_UNLOCK();
584
585 return conf_id;
586 }
587
588
pjsua_vid_win_reset(pjsua_vid_win_id wid)589 void pjsua_vid_win_reset(pjsua_vid_win_id wid)
590 {
591 pjsua_vid_win *w = &pjsua_var.win[wid];
592 pj_pool_t *pool = w->pool;
593
594 pj_bzero(w, sizeof(*w));
595 if (pool) pj_pool_reset(pool);
596 w->ref_cnt = 0;
597 w->pool = pool;
598 w->preview_cap_id = PJMEDIA_VID_INVALID_DEV;
599 }
600
601 /* Allocate and initialize pjsua video window:
602 * - If the type is preview: capture port and render port
603 * will be instantiated, and connected via conf.
604 * - If the type is stream: only render port will be created.
605 */
create_vid_win(pjsua_vid_win_type type,const pjmedia_format * fmt,pjmedia_vid_dev_index rend_id,pjmedia_vid_dev_index cap_id,pj_bool_t show,unsigned wnd_flags,const pjmedia_vid_dev_hwnd * wnd,pjsua_vid_win_id * id)606 static pj_status_t create_vid_win(pjsua_vid_win_type type,
607 const pjmedia_format *fmt,
608 pjmedia_vid_dev_index rend_id,
609 pjmedia_vid_dev_index cap_id,
610 pj_bool_t show,
611 unsigned wnd_flags,
612 const pjmedia_vid_dev_hwnd *wnd,
613 pjsua_vid_win_id *id)
614 {
615 pj_bool_t enable_native_preview;
616 pjsua_vid_win_id wid = PJSUA_INVALID_ID;
617 pjsua_vid_win *w = NULL;
618 pjmedia_vid_port_param vp_param;
619 pjmedia_format fmt_;
620 pj_status_t status;
621 unsigned i;
622
623 enable_native_preview = pjsua_var.media_cfg.vid_preview_enable_native;
624
625 PJ_LOG(4,(THIS_FILE,
626 "Creating video window: type=%s, cap_id=%d, rend_id=%d",
627 pjsua_vid_win_type_name(type), cap_id, rend_id));
628 pj_log_push_indent();
629
630 /* If type is preview, check if it exists already */
631 if (type == PJSUA_WND_TYPE_PREVIEW) {
632 wid = vid_preview_get_win(cap_id, PJ_FALSE);
633 if (wid != PJSUA_INVALID_ID) {
634 /* Yes, it exists */
635 /* Show/hide window */
636 pjmedia_vid_dev_stream *strm;
637 pj_bool_t hide = !show;
638
639 w = &pjsua_var.win[wid];
640
641 PJ_LOG(4,(THIS_FILE,
642 "Window already exists for cap_dev=%d, returning wid=%d",
643 cap_id, wid));
644
645
646 if (w->is_native) {
647 strm = pjmedia_vid_port_get_stream(w->vp_cap);
648 } else {
649 strm = pjmedia_vid_port_get_stream(w->vp_rend);
650 }
651 pj_assert(strm);
652
653 /* Try to apply show/hide, window flags, and output window */
654
655 status = pjmedia_vid_dev_stream_set_cap(
656 strm, PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE,
657 &hide);
658 if (status != PJ_SUCCESS) {
659 PJ_PERROR(4,(THIS_FILE, status,
660 "Ignored error on setting window visibility "
661 "on wid=%d", wid));
662 }
663
664 status = pjmedia_vid_dev_stream_set_cap(
665 strm, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS,
666 &wnd_flags);
667 if (status != PJ_SUCCESS) {
668 PJ_PERROR(4,(THIS_FILE, status,
669 "Ignored error on setting window flags "
670 "on wid=%d", wid));
671 }
672
673 if (wnd) {
674 status = pjmedia_vid_dev_stream_set_cap(
675 strm, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, wnd);
676
677 if (status != PJ_SUCCESS) {
678 PJ_PERROR(4, (THIS_FILE, status,
679 "Ignored error on setting window handle "
680 "on wid=%d", wid));
681 }
682 }
683
684 status = pjsua_vid_conf_connect(w->cap_slot, w->rend_slot, NULL);
685 if (status != PJ_SUCCESS) {
686 PJ_PERROR(4, (THIS_FILE, status,
687 "Ignored error on connecting video ports "
688 "on wid=%d", wid));
689 }
690
691 /* Done */
692 *id = wid;
693 pj_log_pop_indent();
694
695 return PJ_SUCCESS;
696 }
697 }
698
699 /* Allocate window */
700 for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
701 w = &pjsua_var.win[i];
702 if (w->type == PJSUA_WND_TYPE_NONE) {
703 wid = i;
704 w->type = type;
705 break;
706 }
707 }
708 if (i == PJSUA_MAX_VID_WINS) {
709 pj_log_pop_indent();
710 return PJ_ETOOMANY;
711 }
712
713 /* Initialize window */
714 pjmedia_vid_port_param_default(&vp_param);
715
716 if (w->type == PJSUA_WND_TYPE_PREVIEW) {
717 pjmedia_vid_dev_info vdi;
718
719 /*
720 * Determine if the device supports native preview.
721 */
722 status = pjmedia_vid_dev_get_info(cap_id, &vdi);
723 if (status != PJ_SUCCESS)
724 goto on_error;
725
726 if (enable_native_preview &&
727 (vdi.caps & PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW))
728 {
729 /* Device supports native preview! */
730 w->is_native = PJ_TRUE;
731 }
732
733 status = pjmedia_vid_dev_default_param(w->pool, cap_id,
734 &vp_param.vidparam);
735 if (status != PJ_SUCCESS)
736 goto on_error;
737
738 if (w->is_native) {
739 vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE;
740 vp_param.vidparam.window_hide = !show;
741 vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
742 vp_param.vidparam.window_flags = wnd_flags;
743 }
744
745 /* Normalize capture ID, in case it was set to
746 * PJMEDIA_VID_DEFAULT_CAPTURE_DEV
747 */
748 cap_id = vp_param.vidparam.cap_id;
749
750 /* Assign preview capture device ID */
751 w->preview_cap_id = cap_id;
752
753 /* Create capture video port */
754 vp_param.active = PJ_FALSE;
755 vp_param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
756
757 /* Update the video setting with user preference */
758 #define update_param(cap, field) \
759 if ((pjsua_var.vid_caps[cap_id] & cap) && (vdi.caps & cap)) { \
760 vp_param.vidparam.flags |= cap; \
761 pj_memcpy(&vp_param.vidparam.field, \
762 &pjsua_var.vid_param[cap_id].field, \
763 sizeof(vp_param.vidparam.field)); \
764 }
765
766 if (fmt) {
767 vp_param.vidparam.fmt = *fmt;
768 } else {
769 update_param(PJMEDIA_VID_DEV_CAP_FORMAT, fmt);
770 }
771
772 update_param(PJMEDIA_VID_DEV_CAP_ORIENTATION, orient);
773
774 #undef update_param
775
776 status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_cap);
777 if (status != PJ_SUCCESS)
778 goto on_error;
779
780 /* Update format info */
781 fmt_ = vp_param.vidparam.fmt;
782 fmt = &fmt_;
783
784 /* Register capturer to the video conf */
785 status = pjsua_vid_conf_add_port(
786 w->pool,
787 pjmedia_vid_port_get_passive_port(w->vp_cap),
788 NULL, &w->cap_slot);
789 if (status != PJ_SUCCESS)
790 goto on_error;
791
792 /* If device supports native preview, enable it */
793 if (w->is_native) {
794 pjmedia_vid_dev_stream *cap_dev;
795 pj_bool_t enabled = PJ_TRUE;
796
797 cap_dev = pjmedia_vid_port_get_stream(w->vp_cap);
798 status = pjmedia_vid_dev_stream_set_cap(
799 cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW,
800 &enabled);
801 if (status != PJ_SUCCESS) {
802 pjsua_perror(THIS_FILE,
803 "Error activating native preview, falling back "
804 "to software preview..",
805 status);
806 w->is_native = PJ_FALSE;
807 }
808 }
809 }
810
811 /* Create renderer video port, only if it's not a native preview */
812 if (!w->is_native) {
813 status = pjmedia_vid_dev_default_param(w->pool, rend_id,
814 &vp_param.vidparam);
815 if (status != PJ_SUCCESS)
816 goto on_error;
817
818 vp_param.active = PJ_FALSE;
819 vp_param.vidparam.dir = PJMEDIA_DIR_RENDER;
820 vp_param.vidparam.fmt = *fmt;
821 vp_param.vidparam.disp_size = fmt->det.vid.size;
822 vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE;
823 vp_param.vidparam.window_hide = !show;
824 vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
825 vp_param.vidparam.window_flags = wnd_flags;
826 if (wnd) {
827 vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
828 vp_param.vidparam.window = *wnd;
829 }
830
831 status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_rend);
832 if (status != PJ_SUCCESS)
833 goto on_error;
834
835 /* Register renderer to the video conf */
836 status = pjsua_vid_conf_add_port(
837 w->pool,
838 pjmedia_vid_port_get_passive_port(w->vp_rend),
839 NULL, &w->rend_slot);
840 if (status != PJ_SUCCESS)
841 goto on_error;
842
843 /* For preview window, connect capturer & renderer (via conf) */
844 if (w->type == PJSUA_WND_TYPE_PREVIEW && show) {
845 status = pjsua_vid_conf_connect(w->cap_slot, w->rend_slot, NULL);
846 if (status != PJ_SUCCESS)
847 goto on_error;
848 }
849
850 PJ_LOG(4,(THIS_FILE,
851 "%s window id %d created for cap_dev=%d rend_dev=%d",
852 pjsua_vid_win_type_name(type), wid, cap_id, rend_id));
853 } else {
854 PJ_LOG(4,(THIS_FILE,
855 "Preview window id %d created for cap_dev %d, "
856 "using built-in preview!",
857 wid, cap_id));
858 }
859
860
861 /* Done */
862 *id = wid;
863
864 PJ_LOG(4,(THIS_FILE, "Window %d created", wid));
865 pj_log_pop_indent();
866 return PJ_SUCCESS;
867
868 on_error:
869 free_vid_win(wid);
870 pj_log_pop_indent();
871 return status;
872 }
873
874
free_vid_win(pjsua_vid_win_id wid)875 static void free_vid_win(pjsua_vid_win_id wid)
876 {
877 pjsua_vid_win *w = &pjsua_var.win[wid];
878
879 PJ_LOG(4,(THIS_FILE, "Window %d: destroying..", wid));
880 pj_log_push_indent();
881
882 if (w->vp_cap) {
883 pjsua_vid_conf_remove_port(w->cap_slot);
884 pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL,
885 w->vp_cap);
886 pjmedia_vid_port_stop(w->vp_cap);
887 pjmedia_vid_port_destroy(w->vp_cap);
888 }
889 if (w->vp_rend) {
890 pjsua_vid_conf_remove_port(w->rend_slot);
891 pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL,
892 w->vp_rend);
893 pjmedia_vid_port_stop(w->vp_rend);
894 pjmedia_vid_port_destroy(w->vp_rend);
895 }
896 pjsua_vid_win_reset(wid);
897
898 pj_log_pop_indent();
899 }
900
901
inc_vid_win(pjsua_vid_win_id wid)902 static void inc_vid_win(pjsua_vid_win_id wid)
903 {
904 pjsua_vid_win *w;
905
906 pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS);
907
908 w = &pjsua_var.win[wid];
909 pj_assert(w->type != PJSUA_WND_TYPE_NONE);
910 ++w->ref_cnt;
911 }
912
dec_vid_win(pjsua_vid_win_id wid)913 static void dec_vid_win(pjsua_vid_win_id wid)
914 {
915 pjsua_vid_win *w;
916
917 pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS);
918
919 w = &pjsua_var.win[wid];
920 pj_assert(w->type != PJSUA_WND_TYPE_NONE);
921 if (--w->ref_cnt == 0)
922 free_vid_win(wid);
923 }
924
925 /* Initialize video call media */
pjsua_vid_channel_init(pjsua_call_media * call_med)926 pj_status_t pjsua_vid_channel_init(pjsua_call_media *call_med)
927 {
928 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
929
930 call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev;
931 call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev;
932 call_med->strm.v.strm_dec_slot = PJSUA_INVALID_ID;
933 call_med->strm.v.strm_enc_slot = PJSUA_INVALID_ID;
934 if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) {
935 pjmedia_vid_dev_info info;
936 pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info);
937 call_med->strm.v.rdr_dev = info.id;
938 }
939 if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
940 pjmedia_vid_dev_info info;
941 pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info);
942 call_med->strm.v.cap_dev = info.id;
943 }
944
945 return PJ_SUCCESS;
946 }
947
setup_vid_capture(pjsua_call_media * call_med)948 static pj_status_t setup_vid_capture(pjsua_call_media *call_med)
949 {
950 pjsua_acc *acc_enc = &pjsua_var.acc[call_med->call->acc_id];
951 pjmedia_port *media_port;
952 pjsua_vid_win *w;
953 pjsua_vid_win_id wid;
954 pj_bool_t just_created = PJ_FALSE;
955 pj_status_t status;
956
957 /* Retrieve stream encoding port */
958 status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
959 PJMEDIA_DIR_ENCODING,
960 &media_port);
961 if (status != PJ_SUCCESS)
962 return status;
963
964 PJSUA_LOCK();
965
966 /* Note: calling pjsua_vid_preview_get_win() even though
967 * create_vid_win() will automatically create the window
968 * if it doesn't exist, because create_vid_win() will modify
969 * existing window SHOW/HIDE value.
970 */
971 wid = vid_preview_get_win(call_med->strm.v.cap_dev, PJ_FALSE);
972 if (wid == PJSUA_INVALID_ID) {
973 /* Create preview video window */
974 status = create_vid_win(PJSUA_WND_TYPE_PREVIEW,
975 &media_port->info.fmt,
976 call_med->strm.v.rdr_dev,
977 call_med->strm.v.cap_dev,
978 PJSUA_HIDE_WINDOW,
979 acc_enc->cfg.vid_wnd_flags,
980 NULL,
981 &wid);
982 if (status != PJ_SUCCESS)
983 goto on_error;
984
985 just_created = PJ_TRUE;
986 }
987
988 w = &pjsua_var.win[wid];
989 #if ENABLE_EVENT
990 pjmedia_event_subscribe(NULL, &call_media_on_event,
991 call_med, w->vp_cap);
992 #endif
993
994 /* Connect capturer to stream encoding (via conf) */
995 status = pjsua_vid_conf_connect(w->cap_slot,
996 call_med->strm.v.strm_enc_slot,
997 NULL);
998 if (status != PJ_SUCCESS)
999 goto on_error;
1000
1001 /* Start capturer */
1002 if (just_created) {
1003 status = pjmedia_vid_port_start(w->vp_cap);
1004 if (status != PJ_SUCCESS)
1005 goto on_error;
1006 }
1007
1008 /* Done */
1009 inc_vid_win(wid);
1010 call_med->strm.v.cap_win_id = wid;
1011 PJ_LOG(4,(THIS_FILE, "Call %d media %d: video capture set up with "
1012 "dev %d, wid=%d", call_med->call->index,
1013 call_med->idx, call_med->strm.v.cap_dev, wid));
1014
1015 PJSUA_UNLOCK();
1016
1017 return PJ_SUCCESS;
1018
1019 on_error:
1020 PJSUA_UNLOCK();
1021 return status;
1022 }
1023
1024 /* Internal function: update video channel after SDP negotiation.
1025 * Warning: do not use temporary/flip-flop pool, e.g: inv->pool_prov,
1026 * for creating stream, etc, as after SDP negotiation and when
1027 * the SDP media is not changed, the stream should remain running
1028 * while the temporary/flip-flop pool may be released.
1029 */
pjsua_vid_channel_update(pjsua_call_media * call_med,pj_pool_t * tmp_pool,pjmedia_vid_stream_info * si,const pjmedia_sdp_session * local_sdp,const pjmedia_sdp_session * remote_sdp)1030 pj_status_t pjsua_vid_channel_update(pjsua_call_media *call_med,
1031 pj_pool_t *tmp_pool,
1032 pjmedia_vid_stream_info *si,
1033 const pjmedia_sdp_session *local_sdp,
1034 const pjmedia_sdp_session *remote_sdp)
1035 {
1036 pjsua_call *call = call_med->call;
1037 pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
1038 pjmedia_port *media_port;
1039 pj_status_t status;
1040
1041 PJ_UNUSED_ARG(tmp_pool);
1042 PJ_UNUSED_ARG(local_sdp);
1043 PJ_UNUSED_ARG(remote_sdp);
1044
1045 PJ_LOG(4,(THIS_FILE, "Video channel update.."));
1046 pj_log_push_indent();
1047
1048 si->rtcp_sdes_bye_disabled = pjsua_var.media_cfg.no_rtcp_sdes_bye;;
1049
1050 /* Check if no media is active */
1051 if (si->dir != PJMEDIA_DIR_NONE) {
1052 /* Optionally, application may modify other stream settings here
1053 * (such as jitter buffer parameters, codec ptime, etc.)
1054 */
1055 si->jb_init = pjsua_var.media_cfg.jb_init;
1056 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
1057 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
1058 si->jb_max = pjsua_var.media_cfg.jb_max;
1059
1060 /* Set SSRC and CNAME */
1061 si->ssrc = call_med->ssrc;
1062 si->cname = call->cname;
1063
1064 /* Set RTP timestamp & sequence, normally these value are intialized
1065 * automatically when stream session created, but for some cases (e.g:
1066 * call reinvite, call update) timestamp and sequence need to be kept
1067 * contigue.
1068 */
1069 si->rtp_ts = call_med->rtp_tx_ts;
1070 si->rtp_seq = call_med->rtp_tx_seq;
1071 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
1072
1073 /* Set rate control config from account setting */
1074 si->rc_cfg = acc->cfg.vid_stream_rc_cfg;
1075
1076 /* Set send keyframe config from account setting */
1077 si->sk_cfg = acc->cfg.vid_stream_sk_cfg;
1078
1079 #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
1080 /* Enable/disable stream keep-alive and NAT hole punch. */
1081 si->use_ka = acc->cfg.use_stream_ka;
1082
1083 si->ka_cfg = acc->cfg.stream_ka_cfg;
1084 #endif
1085
1086 /* Try to get shared format ID between the capture device and
1087 * the encoder to avoid format conversion in the capture device.
1088 */
1089 if (si->dir & PJMEDIA_DIR_ENCODING) {
1090 pjmedia_vid_dev_info dev_info;
1091 pjmedia_vid_codec_info *codec_info = &si->codec_info;
1092 unsigned i, j;
1093
1094 status = pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev,
1095 &dev_info);
1096 if (status != PJ_SUCCESS)
1097 goto on_error;
1098
1099 /* Find matched format ID */
1100 for (i = 0; i < codec_info->dec_fmt_id_cnt; ++i) {
1101 for (j = 0; j < dev_info.fmt_cnt; ++j) {
1102 if (codec_info->dec_fmt_id[i] ==
1103 (pjmedia_format_id)dev_info.fmt[j].id)
1104 {
1105 /* Apply the matched format ID to the codec */
1106 si->codec_param->dec_fmt.id =
1107 codec_info->dec_fmt_id[i];
1108
1109 /* Force outer loop to break */
1110 i = codec_info->dec_fmt_id_cnt;
1111 break;
1112 }
1113 }
1114 }
1115 }
1116
1117 if (!call->hanging_up && pjsua_var.ua_cfg.cb.on_stream_precreate) {
1118 pjsua_on_stream_precreate_param prm;
1119 prm.stream_idx = call_med->idx;
1120 prm.stream_info.type = PJMEDIA_TYPE_VIDEO;
1121 prm.stream_info.info.vid = *si;
1122 (*pjsua_var.ua_cfg.cb.on_stream_precreate)(call->index, &prm);
1123
1124 /* Copy back only the fields which are allowed to be changed. */
1125 si->jb_init = prm.stream_info.info.vid.jb_init;
1126 si->jb_min_pre = prm.stream_info.info.vid.jb_min_pre;
1127 si->jb_max_pre = prm.stream_info.info.vid.jb_max_pre;
1128 si->jb_max = prm.stream_info.info.vid.jb_max;
1129 #if defined(PJMEDIA_STREAM_ENABLE_KA) && (PJMEDIA_STREAM_ENABLE_KA != 0)
1130 si->use_ka = prm.stream_info.info.vid.use_ka;
1131 #endif
1132 si->rtcp_sdes_bye_disabled = prm.stream_info.info.vid.rtcp_sdes_bye_disabled;
1133 }
1134
1135 /* Create session based on session info. */
1136 status = pjmedia_vid_stream_create(pjsua_var.med_endpt, NULL, si,
1137 call_med->tp, NULL,
1138 &call_med->strm.v.stream);
1139 if (status != PJ_SUCCESS)
1140 goto on_error;
1141
1142 /* Subscribe to video stream events */
1143 pjmedia_event_subscribe(NULL, &call_media_on_event,
1144 call_med, call_med->strm.v.stream);
1145
1146 /* Start stream */
1147 status = pjmedia_vid_stream_start(call_med->strm.v.stream);
1148 if (status != PJ_SUCCESS)
1149 goto on_error;
1150
1151 if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE)
1152 pjmedia_vid_stream_send_rtcp_sdes(call_med->strm.v.stream);
1153
1154 /* Setup decoding direction */
1155 if (si->dir & PJMEDIA_DIR_DECODING)
1156 {
1157 pjsua_vid_win_id wid;
1158 pjsua_vid_win *w;
1159
1160 PJ_LOG(4,(THIS_FILE, "Setting up RX.."));
1161 pj_log_push_indent();
1162
1163 /* Retrieve stream decoding port */
1164 status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
1165 PJMEDIA_DIR_DECODING,
1166 &media_port);
1167 if (status != PJ_SUCCESS) {
1168 pj_log_pop_indent();
1169 goto on_error;
1170 }
1171
1172 /* Create stream video window */
1173 PJSUA_LOCK();
1174 status = create_vid_win(PJSUA_WND_TYPE_STREAM,
1175 &media_port->info.fmt,
1176 call_med->strm.v.rdr_dev,
1177 //acc->cfg.vid_rend_dev,
1178 PJSUA_INVALID_ID,
1179 acc->cfg.vid_in_auto_show,
1180 acc->cfg.vid_wnd_flags,
1181 NULL,
1182 &wid);
1183 if (status != PJ_SUCCESS) {
1184 PJSUA_UNLOCK();
1185 pj_log_pop_indent();
1186 goto on_error;
1187 }
1188
1189 w = &pjsua_var.win[wid];
1190
1191 #if ENABLE_EVENT
1192 /* Register to video events */
1193 pjmedia_event_subscribe(NULL, &call_media_on_event,
1194 call_med, w->vp_rend);
1195 pjmedia_event_subscribe(NULL, &call_media_on_event,
1196 call_med, media_port);
1197 #endif
1198
1199 /* Register renderer to stream events */
1200 pjmedia_vid_port_subscribe_event(w->vp_rend, media_port);
1201
1202 /* Register stream decoding to conf, using tmp_pool should be fine
1203 * as bridge will create its own pool (using tmp_pool factory).
1204 */
1205 status = pjsua_vid_conf_add_port(tmp_pool, media_port, NULL,
1206 &call_med->strm.v.strm_dec_slot);
1207 if (status != PJ_SUCCESS) {
1208 PJSUA_UNLOCK();
1209 pj_log_pop_indent();
1210 goto on_error;
1211 }
1212
1213 /* Connect stream to renderer (via conf) */
1214 status = pjsua_vid_conf_connect(call_med->strm.v.strm_dec_slot,
1215 w->rend_slot, NULL);
1216 if (status != PJ_SUCCESS) {
1217 PJSUA_UNLOCK();
1218 pj_log_pop_indent();
1219 goto on_error;
1220 }
1221
1222 /* Start renderer */
1223 status = pjmedia_vid_port_start(w->vp_rend);
1224 if (status != PJ_SUCCESS) {
1225 PJSUA_UNLOCK();
1226 pj_log_pop_indent();
1227 goto on_error;
1228 }
1229
1230 /* Done */
1231 inc_vid_win(wid);
1232 call_med->strm.v.rdr_win_id = wid;
1233 PJSUA_UNLOCK();
1234 pj_log_pop_indent();
1235 }
1236
1237 /* Retrieve stream encoding port */
1238 status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
1239 PJMEDIA_DIR_ENCODING,
1240 &media_port);
1241 if (status != PJ_SUCCESS)
1242 goto on_error;
1243
1244 /* Register stream encoding to conf, using tmp_pool should be fine
1245 * as bridge will create its own pool (using tmp_pool factory).
1246 */
1247 status = pjsua_vid_conf_add_port(tmp_pool, media_port, NULL,
1248 &call_med->strm.v.strm_enc_slot);
1249 if (status != PJ_SUCCESS)
1250 goto on_error;
1251
1252 /* Setup encoding direction */
1253 if (si->dir & PJMEDIA_DIR_ENCODING) {
1254 PJ_LOG(4,(THIS_FILE, "Setting up TX.."));
1255 pj_log_push_indent();
1256
1257 if (!call->local_hold && acc->cfg.vid_out_auto_transmit) {
1258 status = setup_vid_capture(call_med);
1259 if (status != PJ_SUCCESS)
1260 goto on_error;
1261 }
1262
1263 pj_log_pop_indent();
1264 }
1265 }
1266
1267 if (!acc->cfg.vid_out_auto_transmit && call_med->strm.v.stream) {
1268 status = pjmedia_vid_stream_pause(call_med->strm.v.stream,
1269 PJMEDIA_DIR_ENCODING);
1270 if (status != PJ_SUCCESS)
1271 goto on_error;
1272 }
1273
1274 pj_log_pop_indent();
1275 return PJ_SUCCESS;
1276
1277 on_error:
1278 pj_log_pop_indent();
1279 return status;
1280 }
1281
1282
1283 /* Internal function to stop video stream */
pjsua_vid_stop_stream(pjsua_call_media * call_med)1284 void pjsua_vid_stop_stream(pjsua_call_media *call_med)
1285 {
1286 pjmedia_vid_stream *strm = call_med->strm.v.stream;
1287 pjmedia_rtcp_stat stat;
1288
1289 pj_assert(call_med->type == PJMEDIA_TYPE_VIDEO);
1290
1291 if (!strm)
1292 return;
1293
1294 PJ_LOG(4,(THIS_FILE, "Stopping video stream.."));
1295 pj_log_push_indent();
1296
1297 pjmedia_vid_stream_send_rtcp_bye(strm);
1298
1299 PJSUA_LOCK();
1300
1301 /* Unsubscribe events first, otherwise the event callbacks
1302 * can be called and access already destroyed objects.
1303 */
1304 if (call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) {
1305 pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.cap_win_id];
1306
1307 /* Unsubscribe event */
1308 pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
1309 w->vp_cap);
1310 }
1311 if (call_med->strm.v.rdr_win_id != PJSUA_INVALID_ID) {
1312 pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.rdr_win_id];
1313
1314 /* Unsubscribe event, but stop the render first */
1315 pjmedia_vid_port_stop(w->vp_rend);
1316 pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
1317 w->vp_rend);
1318 }
1319 /* Unsubscribe from video stream events */
1320 pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med, strm);
1321
1322 /* Now that we have unsubscribed from all events, we no longer
1323 * receive future events. But we may have scheduled some timers
1324 * to call media event callbacks, so we need to wait until those
1325 * complete. Note that we can't cancel those timers since timer
1326 * implementation doesn't guarantee there's no race between
1327 * entry cancellation and the callback being called from poll.
1328 */
1329 while (1) {
1330 pjsua_timer_list *act_timer;
1331
1332 act_timer = pjsua_var.active_timer_list.next;
1333 while (act_timer != &pjsua_var.active_timer_list) {
1334 if (act_timer->cb == &call_med_event_cb) {
1335 pjsua_event_list *eve;
1336
1337 eve = (pjsua_event_list *)act_timer->user_data;
1338
1339 if (eve->call_id == call_med->call->index &&
1340 eve->med_idx == call_med->idx)
1341 {
1342 PJSUA_UNLOCK();
1343 pj_thread_sleep(20);
1344 PJSUA_LOCK();
1345 break;
1346 }
1347 }
1348 act_timer = act_timer->next;
1349 }
1350 if (act_timer == &pjsua_var.active_timer_list)
1351 break;
1352 }
1353
1354 if (call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) {
1355 /* Decrement ref count of preview video window */
1356 dec_vid_win(call_med->strm.v.cap_win_id);
1357 call_med->strm.v.cap_win_id = PJSUA_INVALID_ID;
1358
1359 PJ_LOG(4,(THIS_FILE, "Call %d media %d: Preview video window "
1360 "released", call_med->call->index,
1361 call_med->idx));
1362 }
1363
1364 if (call_med->strm.v.rdr_win_id != PJSUA_INVALID_ID) {
1365 /* Decrement ref count of stream video window */
1366 dec_vid_win(call_med->strm.v.rdr_win_id);
1367 call_med->strm.v.rdr_win_id = PJSUA_INVALID_ID;
1368
1369 PJ_LOG(4,(THIS_FILE, "Call %d media %d: Stream video window "
1370 "released", call_med->call->index,
1371 call_med->idx));
1372 }
1373 PJSUA_UNLOCK();
1374
1375 /* Unregister video stream ports (encode+decode) from conference */
1376 if (call_med->strm.v.strm_enc_slot != PJSUA_INVALID_ID) {
1377 pjsua_vid_conf_remove_port(call_med->strm.v.strm_enc_slot);
1378 call_med->strm.v.strm_enc_slot = PJSUA_INVALID_ID;
1379 }
1380 if (call_med->strm.v.strm_dec_slot != PJSUA_INVALID_ID) {
1381 pjsua_vid_conf_remove_port(call_med->strm.v.strm_dec_slot);
1382 call_med->strm.v.strm_dec_slot = PJSUA_INVALID_ID;
1383 }
1384
1385 /* Don't check for direction and transmitted packets count as we
1386 * assume that RTP timestamp remains increasing when outgoing
1387 * direction is disabled/paused.
1388 */
1389 //if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
1390 // (pjmedia_vid_stream_get_stat(strm, &stat) == PJ_SUCCESS) &&
1391 // stat.tx.pkt)
1392 if (pjmedia_vid_stream_get_stat(strm, &stat) == PJ_SUCCESS)
1393 {
1394 /* Save RTP timestamp & sequence, so when media session is
1395 * restarted, those values will be restored as the initial
1396 * RTP timestamp & sequence of the new media session. So in
1397 * the same call session, RTP timestamp and sequence are
1398 * guaranteed to be contigue.
1399 */
1400 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
1401 call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
1402 call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
1403 }
1404
1405 pjmedia_vid_stream_destroy(strm);
1406 call_med->strm.v.stream = NULL;
1407
1408 pj_log_pop_indent();
1409 }
1410
1411 /*
1412 * Does it have built-in preview support.
1413 */
pjsua_vid_preview_has_native(pjmedia_vid_dev_index id)1414 PJ_DEF(pj_bool_t) pjsua_vid_preview_has_native(pjmedia_vid_dev_index id)
1415 {
1416 pjmedia_vid_dev_info vdi;
1417
1418 return (pjmedia_vid_dev_get_info(id, &vdi)==PJ_SUCCESS) ?
1419 ((vdi.caps & PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW)!=0) : PJ_FALSE;
1420 }
1421
1422 /*
1423 * Start video preview window for the specified capture device.
1424 */
pjsua_vid_preview_start(pjmedia_vid_dev_index id,const pjsua_vid_preview_param * prm)1425 PJ_DEF(pj_status_t) pjsua_vid_preview_start(pjmedia_vid_dev_index id,
1426 const pjsua_vid_preview_param *prm)
1427 {
1428 pjsua_vid_win_id wid;
1429 pjsua_vid_win *w;
1430 pjmedia_vid_dev_index rend_id;
1431 pjsua_vid_preview_param default_param;
1432 const pjmedia_format *fmt = NULL;
1433 pj_status_t status;
1434
1435 if (!prm) {
1436 pjsua_vid_preview_param_default(&default_param);
1437 prm = &default_param;
1438 }
1439
1440 PJ_LOG(4,(THIS_FILE, "Starting preview for cap_dev=%d, show=%d",
1441 id, prm->show));
1442 pj_log_push_indent();
1443
1444 PJSUA_LOCK();
1445
1446 rend_id = prm->rend_id;
1447
1448 if (prm->format.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO)
1449 fmt = &prm->format;
1450 status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, fmt, rend_id, id,
1451 prm->show, prm->wnd_flags,
1452 (prm->wnd.info.window? &prm->wnd: NULL), &wid);
1453 if (status != PJ_SUCCESS) {
1454 PJSUA_UNLOCK();
1455 pj_log_pop_indent();
1456 return status;
1457 }
1458
1459 w = &pjsua_var.win[wid];
1460 if (w->preview_running) {
1461 PJSUA_UNLOCK();
1462 pj_log_pop_indent();
1463 return PJ_SUCCESS;
1464 }
1465
1466 /* Start renderer, unless it's native preview */
1467 if (w->is_native && !pjmedia_vid_port_is_running(w->vp_cap)) {
1468 pjmedia_vid_dev_stream *cap_dev;
1469 pj_bool_t enabled = PJ_TRUE;
1470
1471 cap_dev = pjmedia_vid_port_get_stream(w->vp_cap);
1472 status = pjmedia_vid_dev_stream_set_cap(
1473 cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW,
1474 &enabled);
1475 if (status != PJ_SUCCESS) {
1476 pjsua_perror(THIS_FILE,
1477 "Error activating native preview, falling back "
1478 "to software preview..",
1479 status);
1480 w->is_native = PJ_FALSE;
1481 }
1482 }
1483
1484 if (!w->is_native && !pjmedia_vid_port_is_running(w->vp_rend)) {
1485 status = pjmedia_vid_port_start(w->vp_rend);
1486 if (status != PJ_SUCCESS) {
1487 PJSUA_UNLOCK();
1488 pj_log_pop_indent();
1489 return status;
1490 }
1491 }
1492
1493 /* Start capturer */
1494 if (!pjmedia_vid_port_is_running(w->vp_cap)) {
1495 status = pjmedia_vid_port_start(w->vp_cap);
1496 if (status != PJ_SUCCESS) {
1497 PJSUA_UNLOCK();
1498 pj_log_pop_indent();
1499 return status;
1500 }
1501 }
1502
1503 inc_vid_win(wid);
1504 w->preview_running = PJ_TRUE;
1505
1506 PJSUA_UNLOCK();
1507 pj_log_pop_indent();
1508 return PJ_SUCCESS;
1509 }
1510
1511 /*
1512 * Stop video preview.
1513 */
pjsua_vid_preview_stop(pjmedia_vid_dev_index id)1514 PJ_DEF(pj_status_t) pjsua_vid_preview_stop(pjmedia_vid_dev_index id)
1515 {
1516 pjsua_vid_win_id wid = PJSUA_INVALID_ID;
1517 pjsua_vid_win *w;
1518 pj_status_t status;
1519
1520 PJSUA_LOCK();
1521 wid = pjsua_vid_preview_get_win(id);
1522 if (wid == PJSUA_INVALID_ID) {
1523 PJSUA_UNLOCK();
1524 return PJ_ENOTFOUND;
1525 }
1526
1527 PJ_LOG(4,(THIS_FILE, "Stopping preview for cap_dev=%d", id));
1528 pj_log_push_indent();
1529
1530 w = &pjsua_var.win[wid];
1531 if (w->preview_running) {
1532 if (w->is_native) {
1533 pjmedia_vid_dev_stream *cap_dev;
1534 pj_bool_t enabled = PJ_FALSE;
1535
1536 cap_dev = pjmedia_vid_port_get_stream(w->vp_cap);
1537 status = pjmedia_vid_dev_stream_set_cap(
1538 cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW,
1539 &enabled);
1540 } else {
1541 status = pjsua_vid_conf_disconnect(w->cap_slot, w->rend_slot);
1542 if (status != PJ_SUCCESS) {
1543 PJ_PERROR(4, (THIS_FILE, status,
1544 "Ignored error on disconnecting video ports "
1545 "on stopping preview wid=%d", wid));
1546 }
1547 status = pjmedia_vid_port_stop(w->vp_rend);
1548 }
1549
1550 if (status != PJ_SUCCESS) {
1551 PJ_PERROR(1,(THIS_FILE, status, "Error stopping %spreview",
1552 (w->is_native ? "native " : "")));
1553 PJSUA_UNLOCK();
1554 pj_log_pop_indent();
1555 return status;
1556 }
1557
1558 dec_vid_win(wid);
1559 w->preview_running = PJ_FALSE;
1560 }
1561
1562 PJSUA_UNLOCK();
1563 pj_log_pop_indent();
1564
1565 return PJ_SUCCESS;
1566 }
1567
1568
1569 /*****************************************************************************
1570 * Window
1571 */
1572
1573
1574 /*
1575 * Enumerates all video windows.
1576 */
pjsua_vid_enum_wins(pjsua_vid_win_id wids[],unsigned * count)1577 PJ_DEF(pj_status_t) pjsua_vid_enum_wins( pjsua_vid_win_id wids[],
1578 unsigned *count)
1579 {
1580 unsigned i, cnt;
1581
1582 cnt = 0;
1583
1584 for (i=0; i<PJSUA_MAX_VID_WINS && cnt <*count; ++i) {
1585 pjsua_vid_win *w = &pjsua_var.win[i];
1586 if (w->type != PJSUA_WND_TYPE_NONE)
1587 wids[cnt++] = i;
1588 }
1589
1590 *count = cnt;
1591
1592 return PJ_SUCCESS;
1593 }
1594
1595
1596 /*
1597 * Get window info.
1598 */
pjsua_vid_win_get_info(pjsua_vid_win_id wid,pjsua_vid_win_info * wi)1599 PJ_DEF(pj_status_t) pjsua_vid_win_get_info( pjsua_vid_win_id wid,
1600 pjsua_vid_win_info *wi)
1601 {
1602 pjsua_vid_win *w;
1603 pjmedia_vid_dev_stream *s;
1604 pjmedia_vid_dev_param vparam;
1605 pj_status_t status;
1606
1607 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && wi, PJ_EINVAL);
1608
1609 pj_bzero(wi, sizeof(*wi));
1610
1611 PJSUA_LOCK();
1612 w = &pjsua_var.win[wid];
1613
1614 wi->is_native = w->is_native;
1615
1616 if (w->is_native) {
1617 pjmedia_vid_dev_stream *cap_strm;
1618 pjmedia_vid_dev_cap cap = PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
1619
1620 if (!w->vp_cap) {
1621 status = PJ_EINVAL;
1622 } else {
1623 cap_strm = pjmedia_vid_port_get_stream(w->vp_cap);
1624 if (!cap_strm) {
1625 status = PJ_EINVAL;
1626 } else {
1627 status = pjmedia_vid_dev_stream_get_cap(cap_strm, cap,
1628 &wi->hwnd);
1629 }
1630 }
1631 PJSUA_UNLOCK();
1632 return status;
1633 }
1634
1635 if (w->vp_rend == NULL) {
1636 PJSUA_UNLOCK();
1637 return PJ_EINVAL;
1638 }
1639
1640 s = pjmedia_vid_port_get_stream(w->vp_rend);
1641 if (s == NULL) {
1642 PJSUA_UNLOCK();
1643 return PJ_EINVAL;
1644 }
1645
1646 status = pjmedia_vid_dev_stream_get_param(s, &vparam);
1647 if (status != PJ_SUCCESS) {
1648 PJSUA_UNLOCK();
1649 return status;
1650 }
1651
1652 wi->rdr_dev = vparam.rend_id;
1653 wi->slot_id = w->rend_slot;
1654 wi->hwnd = vparam.window;
1655 wi->show = !vparam.window_hide;
1656 wi->pos = vparam.window_pos;
1657 wi->size = vparam.disp_size;
1658
1659 PJSUA_UNLOCK();
1660
1661 return PJ_SUCCESS;
1662 }
1663
1664 /*
1665 * Show or hide window.
1666 */
pjsua_vid_win_set_show(pjsua_vid_win_id wid,pj_bool_t show)1667 PJ_DEF(pj_status_t) pjsua_vid_win_set_show( pjsua_vid_win_id wid,
1668 pj_bool_t show)
1669 {
1670 pjsua_vid_win *w;
1671 pjmedia_vid_dev_stream *s;
1672 pj_bool_t hide;
1673 pj_status_t status;
1674
1675 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL);
1676
1677 PJSUA_LOCK();
1678
1679 w = &pjsua_var.win[wid];
1680 s = pjmedia_vid_port_get_stream(w->vp_rend? w->vp_rend: w->vp_cap);
1681 if (s == NULL) {
1682 PJSUA_UNLOCK();
1683 return PJ_EINVAL;
1684 }
1685
1686 /* Make sure that renderer gets started before shown up */
1687 if (show && !pjmedia_vid_port_is_running(w->vp_rend))
1688 status = pjmedia_vid_port_start(w->vp_rend);
1689
1690 hide = !show;
1691 status = pjmedia_vid_dev_stream_set_cap(s,
1692 PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE, &hide);
1693
1694 PJSUA_UNLOCK();
1695
1696 return status;
1697 }
1698
1699 /*
1700 * Set video window position.
1701 */
pjsua_vid_win_set_pos(pjsua_vid_win_id wid,const pjmedia_coord * pos)1702 PJ_DEF(pj_status_t) pjsua_vid_win_set_pos( pjsua_vid_win_id wid,
1703 const pjmedia_coord *pos)
1704 {
1705 pjsua_vid_win *w;
1706 pjmedia_vid_dev_stream *s;
1707 pj_status_t status;
1708
1709 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && pos, PJ_EINVAL);
1710
1711 PJSUA_LOCK();
1712
1713 w = &pjsua_var.win[wid];
1714 s = pjmedia_vid_port_get_stream(w->vp_rend? w->vp_rend: w->vp_cap);
1715 if (s == NULL) {
1716 PJSUA_UNLOCK();
1717 return PJ_EINVAL;
1718 }
1719
1720 status = pjmedia_vid_dev_stream_set_cap(s,
1721 PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION, pos);
1722
1723 PJSUA_UNLOCK();
1724
1725 return status;
1726 }
1727
1728 /*
1729 * Resize window.
1730 */
pjsua_vid_win_set_size(pjsua_vid_win_id wid,const pjmedia_rect_size * size)1731 PJ_DEF(pj_status_t) pjsua_vid_win_set_size( pjsua_vid_win_id wid,
1732 const pjmedia_rect_size *size)
1733 {
1734 pjsua_vid_win *w;
1735 pjmedia_vid_dev_stream *s;
1736 pj_status_t status;
1737
1738 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && size, PJ_EINVAL);
1739
1740 PJSUA_LOCK();
1741
1742 w = &pjsua_var.win[wid];
1743 s = pjmedia_vid_port_get_stream(w->vp_rend? w->vp_rend: w->vp_cap);
1744 if (s == NULL) {
1745 PJSUA_UNLOCK();
1746 return PJ_EINVAL;
1747 }
1748
1749 status = pjmedia_vid_dev_stream_set_cap(s,
1750 PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE, size);
1751
1752 PJSUA_UNLOCK();
1753
1754 return status;
1755 }
1756
1757 /*
1758 * Set output window.
1759 */
pjsua_vid_win_set_win(pjsua_vid_win_id wid,const pjmedia_vid_dev_hwnd * win)1760 PJ_DEF(pj_status_t) pjsua_vid_win_set_win( pjsua_vid_win_id wid,
1761 const pjmedia_vid_dev_hwnd *win)
1762 {
1763 pjsua_vid_win *w;
1764 pjmedia_vid_dev_stream *s;
1765 pj_status_t status;
1766
1767 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && win, PJ_EINVAL);
1768
1769 PJSUA_LOCK();
1770
1771 w = &pjsua_var.win[wid];
1772 s = pjmedia_vid_port_get_stream(w->vp_rend? w->vp_rend: w->vp_cap);
1773 if (s == NULL) {
1774 PJSUA_UNLOCK();
1775 return PJ_EINVAL;
1776 }
1777
1778 status = pjmedia_vid_dev_stream_set_cap(s,
1779 PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, win);
1780
1781 PJSUA_UNLOCK();
1782
1783 return status;
1784 }
1785
1786 /*
1787 * Set video orientation.
1788 */
pjsua_vid_win_rotate(pjsua_vid_win_id wid,int angle)1789 PJ_DEF(pj_status_t) pjsua_vid_win_rotate( pjsua_vid_win_id wid,
1790 int angle)
1791 {
1792 pjsua_vid_win *w;
1793 pjmedia_vid_dev_stream *s;
1794 pjmedia_orient orient;
1795 pj_status_t status;
1796
1797 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL);
1798 PJ_ASSERT_RETURN((angle % 90) == 0, PJ_EINVAL);
1799
1800 /* Normalize angle, so it must be 0, 90, 180, or 270. */
1801 angle %= 360;
1802 if (angle < 0)
1803 angle += 360;
1804
1805 /* Convert angle to pjmedia_orient */
1806 switch(angle) {
1807 case 0:
1808 /* No rotation */
1809 return PJ_SUCCESS;
1810 case 90:
1811 orient = PJMEDIA_ORIENT_ROTATE_90DEG;
1812 break;
1813 case 180:
1814 orient = PJMEDIA_ORIENT_ROTATE_180DEG;
1815 break;
1816 case 270:
1817 orient = PJMEDIA_ORIENT_ROTATE_270DEG;
1818 break;
1819 default:
1820 pj_assert(!"Angle must have been validated");
1821 return PJ_EBUG;
1822 }
1823
1824 PJSUA_LOCK();
1825
1826 w = &pjsua_var.win[wid];
1827 s = pjmedia_vid_port_get_stream(w->vp_rend? w->vp_rend: w->vp_cap);
1828 if (s == NULL) {
1829 PJSUA_UNLOCK();
1830 return PJ_EINVAL;
1831 }
1832
1833 status = pjmedia_vid_dev_stream_set_cap(s,
1834 PJMEDIA_VID_DEV_CAP_ORIENTATION, &orient);
1835
1836 PJSUA_UNLOCK();
1837
1838 return status;
1839 }
1840
1841
1842 /*
1843 * Set video window fullscreen.
1844 */
pjsua_vid_win_set_fullscreen(pjsua_vid_win_id wid,pj_bool_t enabled)1845 PJ_DEF(pj_status_t) pjsua_vid_win_set_fullscreen( pjsua_vid_win_id wid,
1846 pj_bool_t enabled)
1847 {
1848 pjsua_vid_win *w;
1849 pjmedia_vid_dev_stream *s;
1850 pj_status_t status;
1851
1852 PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL);
1853
1854 PJSUA_LOCK();
1855
1856 w = &pjsua_var.win[wid];
1857 s = pjmedia_vid_port_get_stream(w->vp_rend? w->vp_rend: w->vp_cap);
1858 if (s == NULL) {
1859 PJSUA_UNLOCK();
1860 return PJ_EINVAL;
1861 }
1862
1863 status = pjmedia_vid_dev_stream_set_cap(s,
1864 PJMEDIA_VID_DEV_CAP_OUTPUT_FULLSCREEN, &enabled);
1865
1866 PJSUA_UNLOCK();
1867
1868 return status;
1869 }
1870
call_get_vid_strm_info(pjsua_call * call,int * first_active,int * first_inactive,unsigned * active_cnt,unsigned * cnt)1871 static void call_get_vid_strm_info(pjsua_call *call,
1872 int *first_active,
1873 int *first_inactive,
1874 unsigned *active_cnt,
1875 unsigned *cnt)
1876 {
1877 unsigned i, var_cnt = 0;
1878
1879 if (first_active && ++var_cnt)
1880 *first_active = -1;
1881 if (first_inactive && ++var_cnt)
1882 *first_inactive = -1;
1883 if (active_cnt && ++var_cnt)
1884 *active_cnt = 0;
1885 if (cnt && ++var_cnt)
1886 *cnt = 0;
1887
1888 for (i = 0; i < call->med_cnt && var_cnt; ++i) {
1889 if (call->media[i].type == PJMEDIA_TYPE_VIDEO) {
1890 if (call->media[i].dir != PJMEDIA_DIR_NONE)
1891 {
1892 if (first_active && *first_active == -1) {
1893 *first_active = i;
1894 --var_cnt;
1895 }
1896 if (active_cnt)
1897 ++(*active_cnt);
1898 } else if (first_inactive && *first_inactive == -1) {
1899 *first_inactive = i;
1900 --var_cnt;
1901 }
1902 if (cnt)
1903 ++(*cnt);
1904 }
1905 }
1906 }
1907
1908
1909 /* Send SDP reoffer. */
call_reoffer_sdp(pjsua_call_id call_id,pjmedia_sdp_session * sdp)1910 static pj_status_t call_reoffer_sdp(pjsua_call_id call_id,
1911 pjmedia_sdp_session *sdp)
1912 {
1913 pjsua_call *call;
1914 pjsip_tx_data *tdata;
1915 pjsip_dialog *dlg;
1916 pj_status_t status;
1917
1918 status = acquire_call("call_reoffer_sdp()", call_id, &call, &dlg);
1919 if (status != PJ_SUCCESS)
1920 return status;
1921
1922 if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
1923 PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed"));
1924 pjsip_dlg_dec_lock(dlg);
1925 return PJSIP_ESESSIONSTATE;
1926 }
1927
1928 /* Notify application */
1929 if (!call->hanging_up && pjsua_var.ua_cfg.cb.on_call_sdp_created) {
1930 (*pjsua_var.ua_cfg.cb.on_call_sdp_created)(call_id, sdp,
1931 call->inv->pool_prov,
1932 NULL);
1933 }
1934
1935 /* Create re-INVITE with new offer */
1936 status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata);
1937 if (status != PJ_SUCCESS) {
1938 pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
1939 pjsip_dlg_dec_lock(dlg);
1940 return status;
1941 }
1942
1943 /* Send the request */
1944 status = pjsip_inv_send_msg( call->inv, tdata);
1945 if (status != PJ_SUCCESS) {
1946 pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
1947 pjsip_dlg_dec_lock(dlg);
1948 return status;
1949 }
1950
1951 pjsip_dlg_dec_lock(dlg);
1952
1953 return PJ_SUCCESS;
1954 }
1955
1956 /* Add a new video stream into a call */
call_add_video(pjsua_call * call,pjmedia_vid_dev_index cap_dev,pjmedia_dir dir)1957 static pj_status_t call_add_video(pjsua_call *call,
1958 pjmedia_vid_dev_index cap_dev,
1959 pjmedia_dir dir)
1960 {
1961 pj_pool_t *pool = call->inv->pool_prov;
1962 pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg;
1963 pjsua_call_media *call_med;
1964 const pjmedia_sdp_session *current_sdp;
1965 pjmedia_sdp_session *sdp;
1966 pjmedia_sdp_media *sdp_m;
1967 pjmedia_transport_info tpinfo;
1968 unsigned options;
1969 pj_status_t status;
1970
1971 /* Verify media slot availability */
1972 if (call->med_cnt == PJSUA_MAX_CALL_MEDIA)
1973 return PJ_ETOOMANY;
1974
1975 if (pjsua_call_media_is_changing(call)) {
1976 PJ_LOG(1,(THIS_FILE, "Unable to add video" ERR_MEDIA_CHANGING));
1977 return PJ_EINVALIDOP;
1978 }
1979
1980 /* Get active local SDP and clone it */
1981 status = pjmedia_sdp_neg_get_active_local(call->inv->neg, ¤t_sdp);
1982 if (status != PJ_SUCCESS)
1983 return status;
1984
1985 sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, current_sdp);
1986
1987 /* Clean up & sync provisional media before using it */
1988 pjsua_media_prov_revert(call->index);
1989
1990 /* Initialize call media */
1991 call_med = &call->media_prov[call->med_prov_cnt++];
1992 status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO,
1993 &acc_cfg->rtp_cfg, call->secure_level,
1994 NULL, PJ_FALSE, NULL);
1995 if (status != PJ_SUCCESS)
1996 goto on_error;
1997
1998 /* Override default capture device setting */
1999 call_med->strm.v.cap_dev = cap_dev;
2000
2001 /* Init transport media */
2002 options = (call_med->enable_rtcp_mux? PJMEDIA_TPMED_RTCP_MUX: 0);
2003 status = pjmedia_transport_media_create(call_med->tp, pool, options,
2004 NULL, call_med->idx);
2005 if (status != PJ_SUCCESS)
2006 goto on_error;
2007
2008 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
2009
2010 /* Get transport address info */
2011 pjmedia_transport_info_init(&tpinfo);
2012 pjmedia_transport_get_info(call_med->tp, &tpinfo);
2013
2014 /* Create SDP media line */
2015 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
2016 &tpinfo.sock_info, 0, &sdp_m);
2017 if (status != PJ_SUCCESS)
2018 goto on_error;
2019
2020 sdp->media[sdp->media_count++] = sdp_m;
2021
2022 /* Update media direction, if it is not 'sendrecv' */
2023 if (dir != PJMEDIA_DIR_ENCODING_DECODING) {
2024 pjmedia_sdp_attr *a;
2025
2026 /* Remove sendrecv direction attribute, if any */
2027 pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv");
2028
2029 if (dir == PJMEDIA_DIR_ENCODING)
2030 a = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
2031 else if (dir == PJMEDIA_DIR_DECODING)
2032 a = pjmedia_sdp_attr_create(pool, "recvonly", NULL);
2033 else
2034 a = pjmedia_sdp_attr_create(pool, "inactive", NULL);
2035
2036 pjmedia_sdp_media_add_attr(sdp_m, a);
2037 }
2038
2039 /* Update SDP media line by media transport */
2040 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
2041 sdp, NULL, call_med->idx);
2042 if (status != PJ_SUCCESS)
2043 goto on_error;
2044
2045 status = call_reoffer_sdp(call->index, sdp);
2046 if (status != PJ_SUCCESS)
2047 goto on_error;
2048
2049 call->opt.vid_cnt++;
2050
2051 return PJ_SUCCESS;
2052
2053 on_error:
2054 if (call_med->tp) {
2055 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL);
2056 pjmedia_transport_close(call_med->tp);
2057 call_med->tp = call_med->tp_orig = NULL;
2058 }
2059 call->med_prov_cnt = 0;
2060
2061 return status;
2062 }
2063
2064
2065 /* Modify a video stream from a call, i.e: update direction,
2066 * remove/disable.
2067 */
call_modify_video(pjsua_call * call,int med_idx,pjmedia_dir dir,pj_bool_t remove)2068 static pj_status_t call_modify_video(pjsua_call *call,
2069 int med_idx,
2070 pjmedia_dir dir,
2071 pj_bool_t remove)
2072 {
2073 pjsua_call_media *call_med;
2074 const pjmedia_sdp_session *current_sdp;
2075 pjmedia_sdp_session *sdp;
2076 pj_status_t status;
2077
2078 if (pjsua_call_media_is_changing(call)) {
2079 PJ_LOG(1,(THIS_FILE, "Unable to modify video" ERR_MEDIA_CHANGING));
2080 return PJ_EINVALIDOP;
2081 }
2082
2083 /* Verify and normalize media index */
2084 if (med_idx == -1) {
2085 int first_active;
2086
2087 call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
2088 if (first_active == -1)
2089 return PJ_ENOTFOUND;
2090
2091 med_idx = first_active;
2092 }
2093
2094 /* Clean up & sync provisional media before using it */
2095 pjsua_media_prov_revert(call->index);
2096
2097 call_med = &call->media_prov[med_idx];
2098
2099 /* Verify if the stream media type is video */
2100 if (call_med->type != PJMEDIA_TYPE_VIDEO)
2101 return PJ_EINVAL;
2102
2103 /* Verify if the stream dir is not changed */
2104 if ((!remove && call_med->dir == dir) ||
2105 ( remove && (call_med->tp_st == PJSUA_MED_TP_DISABLED ||
2106 call_med->tp == NULL)))
2107 {
2108 return PJ_SUCCESS;
2109 }
2110
2111 /* Get active local SDP and clone it */
2112 status = pjmedia_sdp_neg_get_active_local(call->inv->neg, ¤t_sdp);
2113 if (status != PJ_SUCCESS)
2114 return status;
2115
2116 sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, current_sdp);
2117
2118 pj_assert(med_idx < (int)sdp->media_count);
2119
2120 if (!remove) {
2121 pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg;
2122 pj_pool_t *pool = call->inv->pool_prov;
2123 pjmedia_sdp_media *sdp_m;
2124
2125 /* Enabling video */
2126 if (call_med->dir == PJMEDIA_DIR_NONE) {
2127 unsigned i, vid_cnt = 0;
2128
2129 /* Check if vid_cnt in call option needs to be increased */
2130 for (i = 0; i < call->med_cnt; ++i) {
2131 if (call->media[i].type == PJMEDIA_TYPE_VIDEO &&
2132 call->media[i].dir != PJMEDIA_DIR_NONE)
2133 {
2134 ++vid_cnt;
2135 }
2136 }
2137 if (call->opt.vid_cnt <= vid_cnt)
2138 call->opt.vid_cnt++;
2139 }
2140
2141 status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO,
2142 &acc_cfg->rtp_cfg, call->secure_level,
2143 NULL, PJ_FALSE, NULL);
2144 if (status != PJ_SUCCESS)
2145 goto on_error;
2146
2147 /* Init transport media */
2148 if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) {
2149 unsigned options = (call_med->enable_rtcp_mux?
2150 PJMEDIA_TPMED_RTCP_MUX: 0);
2151 status = pjmedia_transport_media_create(call_med->tp, pool,
2152 options, NULL,
2153 call_med->idx);
2154 if (status != PJ_SUCCESS)
2155 goto on_error;
2156 }
2157
2158 sdp_m = sdp->media[med_idx];
2159
2160 /* Create new SDP media line if the stream is disabled */
2161 if (sdp->media[med_idx]->desc.port == 0) {
2162 pjmedia_transport_info tpinfo;
2163
2164 /* Get transport address info */
2165 pjmedia_transport_info_init(&tpinfo);
2166 pjmedia_transport_get_info(call_med->tp, &tpinfo);
2167
2168 status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
2169 &tpinfo.sock_info, 0, &sdp_m);
2170 if (status != PJ_SUCCESS)
2171 goto on_error;
2172 }
2173
2174 {
2175 pjmedia_sdp_attr *a;
2176
2177 /* Remove any direction attributes */
2178 pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv");
2179 pjmedia_sdp_media_remove_all_attr(sdp_m, "sendonly");
2180 pjmedia_sdp_media_remove_all_attr(sdp_m, "recvonly");
2181 pjmedia_sdp_media_remove_all_attr(sdp_m, "inactive");
2182
2183 /* Update media direction */
2184 if (dir == PJMEDIA_DIR_ENCODING_DECODING)
2185 a = pjmedia_sdp_attr_create(pool, "sendrecv", NULL);
2186 else if (dir == PJMEDIA_DIR_ENCODING)
2187 a = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
2188 else if (dir == PJMEDIA_DIR_DECODING)
2189 a = pjmedia_sdp_attr_create(pool, "recvonly", NULL);
2190 else
2191 a = pjmedia_sdp_attr_create(pool, "inactive", NULL);
2192
2193 pjmedia_sdp_media_add_attr(sdp_m, a);
2194 }
2195
2196 sdp->media[med_idx] = sdp_m;
2197
2198 if (call_med->dir == PJMEDIA_DIR_NONE) {
2199 /* Update SDP media line by media transport */
2200 status = pjmedia_transport_encode_sdp(call_med->tp, pool,
2201 sdp, NULL, call_med->idx);
2202 if (status != PJ_SUCCESS)
2203 goto on_error;
2204 }
2205
2206 on_error:
2207 if (status != PJ_SUCCESS) {
2208 /* Revert back provisional media. */
2209 pjsua_media_prov_revert(call->index);
2210
2211 return status;
2212 }
2213
2214 } else {
2215
2216 pj_pool_t *pool = call->inv->pool_prov;
2217
2218 /* Mark media transport to disabled */
2219 // Don't close this here, as SDP negotiation has not been
2220 // done and stream may be still active.
2221 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);
2222
2223 /* Deactivate the stream */
2224 pjmedia_sdp_media_deactivate(pool, sdp->media[med_idx]);
2225
2226 call->opt.vid_cnt--;
2227 }
2228
2229 status = call_reoffer_sdp(call->index, sdp);
2230 if (status != PJ_SUCCESS)
2231 return status;
2232
2233 return PJ_SUCCESS;
2234 }
2235
2236
2237 /* Change capture device of a video stream in a call */
call_change_cap_dev(pjsua_call * call,int med_idx,pjmedia_vid_dev_index cap_dev)2238 static pj_status_t call_change_cap_dev(pjsua_call *call,
2239 int med_idx,
2240 pjmedia_vid_dev_index cap_dev)
2241 {
2242 pjsua_call_media *call_med;
2243 pjmedia_vid_dev_stream *old_dev;
2244 pjmedia_vid_dev_switch_param switch_prm;
2245 pjmedia_vid_dev_info info;
2246 pjsua_vid_win *w, *new_w = NULL;
2247 pjsua_vid_win_id wid, new_wid;
2248 pjmedia_port *media_port;
2249 pj_status_t status;
2250
2251 /* Verify and normalize media index */
2252 if (med_idx == -1) {
2253 int first_active;
2254
2255 call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
2256 if (first_active == -1)
2257 return PJ_ENOTFOUND;
2258
2259 med_idx = first_active;
2260 }
2261
2262 call_med = &call->media[med_idx];
2263
2264 /* Verify if the stream media type is video */
2265 if (call_med->type != PJMEDIA_TYPE_VIDEO)
2266 return PJ_EINVAL;
2267
2268 /* Verify the capture device */
2269 status = pjmedia_vid_dev_get_info(cap_dev, &info);
2270 if (status != PJ_SUCCESS || info.dir != PJMEDIA_DIR_CAPTURE)
2271 return PJ_EINVAL;
2272
2273 /* The specified capture device is being used already */
2274 if (call_med->strm.v.cap_dev == cap_dev)
2275 return PJ_SUCCESS;
2276
2277 /* == Apply the new capture device == */
2278 PJSUA_LOCK();
2279
2280 /* If media does not have active preview, simply set capture device ID */
2281 if (call_med->strm.v.cap_win_id == PJSUA_INVALID_ID) {
2282 call_med->strm.v.cap_dev = cap_dev;
2283
2284 /* That's it */
2285 goto on_sync_and_return;
2286 }
2287
2288 wid = call_med->strm.v.cap_win_id;
2289 w = &pjsua_var.win[wid];
2290 pj_assert(w->type == PJSUA_WND_TYPE_PREVIEW && w->vp_cap);
2291
2292 /* If the old device supports fast switching, then that's excellent! */
2293 old_dev = pjmedia_vid_port_get_stream(w->vp_cap);
2294 pjmedia_vid_dev_switch_param_default(&switch_prm);
2295 switch_prm.target_id = cap_dev;
2296 status = pjmedia_vid_dev_stream_set_cap(old_dev,
2297 PJMEDIA_VID_DEV_CAP_SWITCH,
2298 &switch_prm);
2299 if (status == PJ_SUCCESS) {
2300 w->preview_cap_id = cap_dev;
2301 call_med->strm.v.cap_dev = cap_dev;
2302
2303 /* Yay, change capturer done! Now return */
2304 goto on_sync_and_return;
2305 }
2306
2307 /* Oh no, it doesn't support fast switching. Do normal change then,
2308 * i.e: remove the old and create a new capture.
2309 */
2310 status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
2311 PJMEDIA_DIR_ENCODING, &media_port);
2312 if (status != PJ_SUCCESS) {
2313 PJSUA_UNLOCK();
2314 return status;
2315 }
2316
2317 pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
2318 w->vp_cap);
2319
2320 /* Disconnect the old capture device to stream encoding port */
2321 status = pjsua_vid_conf_disconnect(w->cap_slot,
2322 call_med->strm.v.strm_enc_slot);
2323 if (status != PJ_SUCCESS) {
2324 PJSUA_UNLOCK();
2325 return status;
2326 }
2327
2328
2329 /* = Attach stream port to the new capture device = */
2330
2331 /* Note: calling pjsua_vid_preview_get_win() even though
2332 * create_vid_win() will automatically create the window
2333 * if it doesn't exist, because create_vid_win() will modify
2334 * existing window SHOW/HIDE value.
2335 */
2336 new_wid = vid_preview_get_win(cap_dev, PJ_FALSE);
2337 if (new_wid == PJSUA_INVALID_ID) {
2338 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
2339
2340 /* Create preview video window */
2341 status = create_vid_win(PJSUA_WND_TYPE_PREVIEW,
2342 &media_port->info.fmt,
2343 call_med->strm.v.rdr_dev,
2344 cap_dev,
2345 PJSUA_HIDE_WINDOW,
2346 acc->cfg.vid_wnd_flags,
2347 NULL,
2348 &new_wid);
2349 if (status != PJ_SUCCESS)
2350 goto on_error;
2351 }
2352
2353 inc_vid_win(new_wid);
2354 new_w = &pjsua_var.win[new_wid];
2355
2356 if (new_w->vp_rend) {
2357 /* Start renderer */
2358 status = pjmedia_vid_port_start(new_w->vp_rend);
2359 if (status != PJ_SUCCESS)
2360 goto on_error;
2361 }
2362
2363 #if ENABLE_EVENT
2364 pjmedia_event_subscribe(NULL, &call_media_on_event,
2365 call_med, new_w->vp_cap);
2366 #endif
2367
2368 /* Start capturer */
2369 if (!pjmedia_vid_port_is_running(new_w->vp_cap)) {
2370 status = pjmedia_vid_port_start(new_w->vp_cap);
2371 if (status != PJ_SUCCESS)
2372 goto on_error;
2373 }
2374
2375 /* Connect capturer to stream encoding port (via conf) */
2376 status = pjsua_vid_conf_connect(new_w->cap_slot,
2377 call_med->strm.v.strm_enc_slot,
2378 NULL);
2379 if (status != PJ_SUCCESS)
2380 goto on_error;
2381
2382 /* Finally */
2383 call_med->strm.v.cap_dev = cap_dev;
2384 call_med->strm.v.cap_win_id = new_wid;
2385 dec_vid_win(wid);
2386
2387 on_sync_and_return:
2388
2389 /* Sync provisional media from call media */
2390 pj_memcpy(&call->media_prov[med_idx], call_med, sizeof(call->media[0]));
2391
2392 PJSUA_UNLOCK();
2393
2394 return PJ_SUCCESS;
2395
2396 on_error:
2397 PJ_PERROR(4,(THIS_FILE, status,
2398 "Call %d: error changing capture device to %d",
2399 call->index, cap_dev));
2400
2401 if (new_w) {
2402 /* Unsubscribe, just in case */
2403 pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
2404 new_w->vp_cap);
2405
2406 /* Release the new capturer */
2407 dec_vid_win(new_wid);
2408 }
2409
2410 /* Revert back to the old capturer */
2411 status = pjsua_vid_conf_connect(w->cap_slot,
2412 call_med->strm.v.strm_enc_slot, NULL);
2413 if (status != PJ_SUCCESS) {
2414 PJSUA_UNLOCK();
2415 return status;
2416 }
2417
2418 #if ENABLE_EVENT
2419 /* Resubscribe */
2420 pjmedia_event_subscribe(NULL, &call_media_on_event,
2421 call_med, w->vp_cap);
2422 #endif
2423
2424 PJSUA_UNLOCK();
2425
2426 return status;
2427 }
2428
2429
2430 /* Start/stop transmitting video stream in a call */
call_set_tx_video(pjsua_call * call,int med_idx,pj_bool_t enable)2431 static pj_status_t call_set_tx_video(pjsua_call *call,
2432 int med_idx,
2433 pj_bool_t enable)
2434 {
2435 pjsua_call_media *call_med;
2436 pj_status_t status;
2437
2438 /* Verify and normalize media index */
2439 if (med_idx == -1) {
2440 int first_active;
2441
2442 call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
2443 if (first_active == -1)
2444 return PJ_ENOTFOUND;
2445
2446 med_idx = first_active;
2447 }
2448
2449 call_med = &call->media[med_idx];
2450
2451 /* Verify if the stream is transmitting video */
2452 if (call_med->type != PJMEDIA_TYPE_VIDEO ||
2453 (enable && (call_med->dir & PJMEDIA_DIR_ENCODING) == 0))
2454 {
2455 return PJ_EINVAL;
2456 }
2457
2458 if (enable) {
2459 if (call_med->strm.v.cap_win_id == PJSUA_INVALID_ID) {
2460 /* Setup the video capture first */
2461 status = setup_vid_capture(call_med);
2462 if (status != PJ_SUCCESS)
2463 return status;
2464 }
2465
2466 /* Resume stream in encoding direction */
2467 status = pjmedia_vid_stream_resume(call_med->strm.v.stream,
2468 PJMEDIA_DIR_ENCODING);
2469 } else {
2470 pjsua_vid_win_id wid;
2471 pjsua_vid_win *w;
2472
2473 /* Pause stream in encoding direction */
2474 status = pjmedia_vid_stream_pause( call_med->strm.v.stream,
2475 PJMEDIA_DIR_ENCODING);
2476
2477 PJSUA_LOCK();
2478
2479 wid = vid_preview_get_win(call_med->strm.v.cap_dev, PJ_FALSE);
2480 if (wid != PJSUA_INVALID_ID) {
2481 w = &pjsua_var.win[wid];
2482
2483 /* Unsubscribe event */
2484 pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
2485 w->vp_cap);
2486
2487 /* Disconnect from video conference */
2488 pjsua_vid_conf_disconnect(w->cap_slot,
2489 call_med->strm.v.strm_enc_slot);
2490
2491 /* Decrement ref count of the video window */
2492 dec_vid_win(call_med->strm.v.cap_win_id);
2493 call_med->strm.v.cap_win_id = PJSUA_INVALID_ID;
2494 }
2495
2496 PJSUA_UNLOCK();
2497 }
2498
2499 /* Sync provisional media from call media */
2500 pj_memcpy(&call->media_prov[med_idx], call_med, sizeof(call->media[0]));
2501
2502 return status;
2503 }
2504
2505
call_send_vid_keyframe(pjsua_call * call,int med_idx)2506 static pj_status_t call_send_vid_keyframe(pjsua_call *call,
2507 int med_idx)
2508 {
2509 pjsua_call_media *call_med;
2510
2511 /* Verify and normalize media index */
2512 if (med_idx == -1) {
2513 int first_active;
2514
2515 call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
2516 if (first_active == -1)
2517 return PJ_ENOTFOUND;
2518
2519 med_idx = first_active;
2520 }
2521
2522 call_med = &call->media[med_idx];
2523
2524 /* Verify media type and stream instance. */
2525 if (call_med->type != PJMEDIA_TYPE_VIDEO || !call_med->strm.v.stream)
2526 return PJ_EINVAL;
2527
2528 return pjmedia_vid_stream_send_keyframe(call_med->strm.v.stream);
2529 }
2530
2531
2532 /*
2533 * Start, stop, and/or manipulate video transmission for the specified call.
2534 */
pjsua_call_set_vid_strm(pjsua_call_id call_id,pjsua_call_vid_strm_op op,const pjsua_call_vid_strm_op_param * param)2535 PJ_DEF(pj_status_t) pjsua_call_set_vid_strm (
2536 pjsua_call_id call_id,
2537 pjsua_call_vid_strm_op op,
2538 const pjsua_call_vid_strm_op_param *param)
2539 {
2540 pjsua_call *call;
2541 pjsip_dialog *dlg = NULL;
2542 pjsua_call_vid_strm_op_param param_;
2543 pj_status_t status;
2544
2545 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
2546 PJ_EINVAL);
2547 PJ_ASSERT_RETURN(op != PJSUA_CALL_VID_STRM_NO_OP, PJ_EINVAL);
2548
2549 PJ_LOG(4,(THIS_FILE, "Call %d: set video stream, op=%d",
2550 call_id, op));
2551 pj_log_push_indent();
2552
2553 status = acquire_call("pjsua_call_set_vid_strm()", call_id, &call, &dlg);
2554 if (status != PJ_SUCCESS)
2555 goto on_return;
2556
2557 if (param) {
2558 param_ = *param;
2559 } else {
2560 pjsua_call_vid_strm_op_param_default(¶m_);
2561 }
2562
2563 /* If set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with
2564 * account default video capture device.
2565 */
2566 if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
2567 pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg;
2568 param_.cap_dev = acc_cfg->vid_cap_dev;
2569
2570 /* If the account default video capture device is
2571 * PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with
2572 * global default video capture device.
2573 */
2574 if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
2575 pjmedia_vid_dev_info info;
2576 pjmedia_vid_dev_get_info(param_.cap_dev, &info);
2577 pj_assert(info.dir == PJMEDIA_DIR_CAPTURE);
2578 param_.cap_dev = info.id;
2579 }
2580 }
2581
2582 switch (op) {
2583 case PJSUA_CALL_VID_STRM_ADD:
2584 status = call_add_video(call, param_.cap_dev, param_.dir);
2585 break;
2586 case PJSUA_CALL_VID_STRM_REMOVE:
2587 status = call_modify_video(call, param_.med_idx, PJMEDIA_DIR_NONE,
2588 PJ_TRUE);
2589 break;
2590 case PJSUA_CALL_VID_STRM_CHANGE_DIR:
2591 status = call_modify_video(call, param_.med_idx, param_.dir, PJ_FALSE);
2592 break;
2593 case PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV:
2594 status = call_change_cap_dev(call, param_.med_idx, param_.cap_dev);
2595 break;
2596 case PJSUA_CALL_VID_STRM_START_TRANSMIT:
2597 status = call_set_tx_video(call, param_.med_idx, PJ_TRUE);
2598 break;
2599 case PJSUA_CALL_VID_STRM_STOP_TRANSMIT:
2600 status = call_set_tx_video(call, param_.med_idx, PJ_FALSE);
2601 break;
2602 case PJSUA_CALL_VID_STRM_SEND_KEYFRAME:
2603 status = call_send_vid_keyframe(call, param_.med_idx);
2604 break;
2605 default:
2606 status = PJ_EINVALIDOP;
2607 break;
2608 }
2609
2610 on_return:
2611 if (dlg) pjsip_dlg_dec_lock(dlg);
2612 pj_log_pop_indent();
2613 return status;
2614 }
2615
2616
2617 /*
2618 * Get the media stream index of the default video stream in the call.
2619 */
pjsua_call_get_vid_stream_idx(pjsua_call_id call_id)2620 PJ_DEF(int) pjsua_call_get_vid_stream_idx(pjsua_call_id call_id)
2621 {
2622 pjsua_call *call;
2623 int first_active, first_inactive;
2624
2625 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
2626 PJ_EINVAL);
2627
2628 PJSUA_LOCK();
2629 call = &pjsua_var.calls[call_id];
2630 call_get_vid_strm_info(call, &first_active, &first_inactive, NULL, NULL);
2631 PJSUA_UNLOCK();
2632
2633 if (first_active == -1)
2634 return first_inactive;
2635
2636 return first_active;
2637 }
2638
2639
2640 /*
2641 * Determine if video stream for the specified call is currently running
2642 * for the specified direction.
2643 */
pjsua_call_vid_stream_is_running(pjsua_call_id call_id,int med_idx,pjmedia_dir dir)2644 PJ_DEF(pj_bool_t) pjsua_call_vid_stream_is_running( pjsua_call_id call_id,
2645 int med_idx,
2646 pjmedia_dir dir)
2647 {
2648 pjsua_call *call;
2649 pjsua_call_media *call_med;
2650
2651 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
2652 PJ_EINVAL);
2653
2654 /* Verify and normalize media index */
2655 if (med_idx == -1) {
2656 med_idx = pjsua_call_get_vid_stream_idx(call_id);
2657 }
2658
2659 call = &pjsua_var.calls[call_id];
2660 PJ_ASSERT_RETURN(med_idx >= 0 && med_idx < (int)call->med_cnt, PJ_EINVAL);
2661
2662 call_med = &call->media[med_idx];
2663
2664 /* Verify if the stream is transmitting video */
2665 if (call_med->type != PJMEDIA_TYPE_VIDEO || (call_med->dir & dir) == 0 ||
2666 !call_med->strm.v.stream)
2667 {
2668 return PJ_FALSE;
2669 }
2670
2671 return pjmedia_vid_stream_is_running(call_med->strm.v.stream, dir);
2672 }
2673
2674
2675 /*****************************************************************************
2676 * Video conference
2677 */
2678
2679 /*
2680 * Get current number of active ports in the bridge.
2681 */
pjsua_vid_conf_get_active_ports(void)2682 PJ_DEF(unsigned) pjsua_vid_conf_get_active_ports(void)
2683 {
2684 return pjmedia_vid_conf_get_port_count(pjsua_var.vid_conf);
2685 }
2686
2687
2688 /*
2689 * Enumerate all video conference ports.
2690 */
pjsua_vid_conf_enum_ports(pjsua_conf_port_id id[],unsigned * count)2691 PJ_DEF(pj_status_t) pjsua_vid_conf_enum_ports( pjsua_conf_port_id id[],
2692 unsigned *count)
2693 {
2694 return pjmedia_vid_conf_enum_ports(pjsua_var.vid_conf,
2695 (unsigned*)id, count);
2696 }
2697
2698
2699 /*
2700 * Get information about the specified video conference port
2701 */
pjsua_vid_conf_get_port_info(pjsua_conf_port_id port_id,pjsua_vid_conf_port_info * info)2702 PJ_DEF(pj_status_t) pjsua_vid_conf_get_port_info(
2703 pjsua_conf_port_id port_id,
2704 pjsua_vid_conf_port_info *info)
2705 {
2706 pjmedia_vid_conf_port_info cinfo;
2707 unsigned i;
2708 pj_status_t status;
2709
2710 status = pjmedia_vid_conf_get_port_info(pjsua_var.vid_conf,
2711 (unsigned)port_id, &cinfo);
2712 if (status != PJ_SUCCESS)
2713 return status;
2714
2715 pj_bzero(info, sizeof(*info));
2716 info->slot_id = port_id;
2717 info->name = cinfo.name;
2718 pjmedia_format_copy(&info->format, &cinfo.format);
2719
2720 /* Build array of listeners */
2721 info->listener_cnt = cinfo.listener_cnt;
2722 for (i=0; i<cinfo.listener_cnt; ++i) {
2723 info->listeners[i] = cinfo.listener_slots[i];
2724 }
2725
2726 /* Build array of transmitters */
2727 info->transmitter_cnt = cinfo.transmitter_cnt;
2728 for (i=0; i<cinfo.transmitter_cnt; ++i) {
2729 info->transmitters[i] = cinfo.transmitter_slots[i];
2730 }
2731
2732 return PJ_SUCCESS;
2733
2734 }
2735
2736
2737 /*
2738 * Add arbitrary video media port to PJSUA's video conference bridge.
2739 */
pjsua_vid_conf_add_port(pj_pool_t * pool,pjmedia_port * port,const void * param,pjsua_conf_port_id * p_id)2740 PJ_DEF(pj_status_t) pjsua_vid_conf_add_port( pj_pool_t *pool,
2741 pjmedia_port *port,
2742 const void *param,
2743 pjsua_conf_port_id *p_id)
2744 {
2745 pj_status_t status;
2746
2747 PJ_UNUSED_ARG(param);
2748
2749 status = pjmedia_vid_conf_add_port(pjsua_var.vid_conf, pool,
2750 port, NULL, NULL, (unsigned*)p_id);
2751 if (status != PJ_SUCCESS) {
2752 if (p_id)
2753 *p_id = PJSUA_INVALID_ID;
2754 }
2755
2756 return status;
2757 }
2758
2759
2760 /*
2761 * Remove arbitrary slot from the video conference bridge.
2762 */
pjsua_vid_conf_remove_port(pjsua_conf_port_id id)2763 PJ_DEF(pj_status_t) pjsua_vid_conf_remove_port(pjsua_conf_port_id id)
2764 {
2765 return pjmedia_vid_conf_remove_port(pjsua_var.vid_conf, (unsigned)id);
2766 }
2767
2768
2769 /*
2770 * Establish unidirectional video flow from souce to sink.
2771 */
pjsua_vid_conf_connect(pjsua_conf_port_id source,pjsua_conf_port_id sink,const void * param)2772 PJ_DEF(pj_status_t) pjsua_vid_conf_connect( pjsua_conf_port_id source,
2773 pjsua_conf_port_id sink,
2774 const void *param)
2775 {
2776 PJ_UNUSED_ARG(param);
2777 return pjmedia_vid_conf_connect_port(pjsua_var.vid_conf, source, sink,
2778 NULL);
2779 }
2780
2781
2782 /*
2783 * Disconnect video flow from the source to destination port.
2784 */
pjsua_vid_conf_disconnect(pjsua_conf_port_id source,pjsua_conf_port_id sink)2785 PJ_DEF(pj_status_t) pjsua_vid_conf_disconnect(pjsua_conf_port_id source,
2786 pjsua_conf_port_id sink)
2787 {
2788 return pjmedia_vid_conf_disconnect_port(pjsua_var.vid_conf, source, sink);
2789 }
2790
2791 /*
2792 * Get the video window associated with the call.
2793 */
pjsua_call_get_vid_win(pjsua_call_id call_id)2794 PJ_DEF(pjsua_vid_win_id) pjsua_call_get_vid_win(pjsua_call_id call_id)
2795 {
2796 pjsua_call *call;
2797 pjsua_vid_win_id wid = PJSUA_INVALID_ID;
2798 unsigned i;
2799
2800 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
2801 PJ_EINVAL);
2802
2803 /* Use PJSUA_LOCK() instead of acquire_call():
2804 * https://trac.pjsip.org/repos/ticket/1371
2805 */
2806 PJSUA_LOCK();
2807
2808 if (!pjsua_call_is_active(call_id))
2809 goto on_return;
2810
2811 call = &pjsua_var.calls[call_id];
2812 for (i = 0; i < call->med_cnt; ++i) {
2813 if (call->media[i].type == PJMEDIA_TYPE_VIDEO &&
2814 (call->media[i].dir & PJMEDIA_DIR_DECODING))
2815 {
2816 wid = call->media[i].strm.v.rdr_win_id;
2817 break;
2818 }
2819 }
2820
2821 on_return:
2822 PJSUA_UNLOCK();
2823
2824 return wid;
2825 }
2826
2827
2828 /*
2829 * Get the video conference port identification associated with the call.
2830 */
pjsua_call_get_vid_conf_port(pjsua_call_id call_id,pjmedia_dir dir)2831 PJ_DEF(pjsua_conf_port_id) pjsua_call_get_vid_conf_port(
2832 pjsua_call_id call_id,
2833 pjmedia_dir dir)
2834 {
2835 pjsua_call *call;
2836 pjsua_conf_port_id port_id = PJSUA_INVALID_ID;
2837 unsigned i;
2838
2839 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
2840 PJ_EINVAL);
2841 PJ_ASSERT_RETURN(dir==PJMEDIA_DIR_ENCODING || dir==PJMEDIA_DIR_DECODING,
2842 PJ_EINVAL);
2843
2844 /* Use PJSUA_LOCK() instead of acquire_call():
2845 * https://trac.pjsip.org/repos/ticket/1371
2846 */
2847 PJSUA_LOCK();
2848
2849 if (!pjsua_call_is_active(call_id))
2850 goto on_return;
2851
2852 call = &pjsua_var.calls[call_id];
2853 for (i = 0; i < call->med_cnt; ++i) {
2854 if (call->media[i].type == PJMEDIA_TYPE_VIDEO &&
2855 (call->media[i].dir & dir))
2856 {
2857 port_id = (dir==PJMEDIA_DIR_ENCODING)?
2858 call->media[i].strm.v.strm_enc_slot :
2859 call->media[i].strm.v.strm_dec_slot;
2860 break;
2861 }
2862 }
2863
2864 on_return:
2865 PJSUA_UNLOCK();
2866
2867 return port_id;
2868 }
2869
2870
2871 #endif /* PJSUA_HAS_VIDEO */
2872
2873 #endif /* PJSUA_MEDIA_HAS_PJMEDIA */
2874