~ubuntu-branches/ubuntu/trusty/sflphone/trusty

« back to all changes in this revision

Viewing changes to daemon/libs/pjproject-2.0.1/pjsip/src/pjsua-lib/pjsua_vid.c

  • Committer: Package Import Robot
  • Author(s): Mark Purcell
  • Date: 2014-01-28 18:23:36 UTC
  • mfrom: (4.3.4 sid)
  • Revision ID: package-import@ubuntu.com-20140128182336-jrsv0k9u6cawc068
Tags: 1.3.0-1
* New upstream release 
  - Fixes "New Upstream Release" (Closes: #735846)
  - Fixes "Ringtone does not stop" (Closes: #727164)
  - Fixes "[sflphone-kde] crash on startup" (Closes: #718178)
  - Fixes "sflphone GUI crashes when call is hung up" (Closes: #736583)
* Build-Depends: ensure GnuTLS 2.6
  - libucommon-dev (>= 6.0.7-1.1), libccrtp-dev (>= 2.0.6-3)
  - Fixes "FTBFS Build-Depends libgnutls{26,28}-dev" (Closes: #722040)
* Fix "boost 1.49 is going away" unversioned Build-Depends: (Closes: #736746)
* Add Build-Depends: libsndfile-dev, nepomuk-core-dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* $Id: pjsua_vid.c 4071 2012-04-24 05:40:32Z nanang $ */
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 */