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