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, &current_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, &current_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(&param_);
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