diff -ruN pjproject-2.10/pjmedia/include/pjmedia-videodev/fb_dev.h pjsip/pjmedia/include/pjmedia-videodev/fb_dev.h
--- pjproject-2.10/pjmedia/include/pjmedia-videodev/fb_dev.h	1970-01-01 01:00:00.000000000 +0100
+++ pjsip/pjmedia/include/pjmedia-videodev/fb_dev.h	2021-02-06 18:43:39.220193284 +0100
@@ -0,0 +1,32 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2014-present AG Projects
+ * Copyright (C) 2013-2014 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef PJMEDIA_VIDEODEV_FB_DEV_H__
+#define PJMEDIA_VIDEODEV_FB_DEV_H__
+
+#include <pjmedia-videodev/videodev_imp.h>
+
+typedef void (*pjmedia_vid_dev_fb_frame_cb)(const pjmedia_frame *frame, const pjmedia_rect_size size, void *user_data);
+
+pj_status_t
+pjmedia_vid_dev_fb_set_callback(pjmedia_vid_dev_stream *strm,
+                                pjmedia_vid_dev_fb_frame_cb cb,
+                                void *user_data);
+
+#endif    /* PJMEDIA_VIDEODEV_FB_DEV_H__ */
diff -ruN pjproject-2.10/pjmedia/include/pjmedia_videodev.h pjsip/pjmedia/include/pjmedia_videodev.h
--- pjproject-2.10/pjmedia/include/pjmedia_videodev.h	2020-02-14 10:48:27.000000000 +0100
+++ pjsip/pjmedia/include/pjmedia_videodev.h	2021-02-06 18:39:51.429682633 +0100
@@ -27,5 +27,6 @@
 #include <pjmedia-videodev/videodev.h>
 #include <pjmedia-videodev/videodev_imp.h>
 #include <pjmedia-videodev/avi_dev.h>
+#include <pjmedia-videodev/fb_dev.h>
 
 #endif	/* __PJMEDIA_VIDEODEV_H__ */
diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/avf_dev.m pjsip/pjmedia/src/pjmedia-videodev/avf_dev.m
--- pjproject-2.10/pjmedia/src/pjmedia-videodev/avf_dev.m	1970-01-01 01:00:00.000000000 +0100
+++ pjsip/pjmedia/src/pjmedia-videodev/avf_dev.m	2021-02-06 18:46:39.045322874 +0100
@@ -0,0 +1,682 @@
+/*
+ * Copyright (C) 2014-present AG Projects (http://ag-projects.com)
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <pjmedia-videodev/videodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \
+    defined(PJMEDIA_VIDEO_DEV_HAS_AVF) && PJMEDIA_VIDEO_DEV_HAS_AVF != 0
+
+#include <Foundation/NSAutoreleasePool.h>
+#include <AVFoundation/AVFoundation.h>
+#include <QuartzCore/QuartzCore.h>
+#include <dispatch/dispatch.h>
+
+#define THIS_FILE		"avf_dev.c"
+#define DEFAULT_CLOCK_RATE	90000
+#define DEFAULT_WIDTH		640
+#define DEFAULT_HEIGHT		480
+#define DEFAULT_FPS		15
+
+
+typedef struct avf_fmt_info
+{
+    pjmedia_format_id   pjmedia_format;
+    unsigned		avf_format;
+} avf_fmt_info;
+
+static avf_fmt_info avf_fmts[] =
+{
+    {PJMEDIA_FORMAT_BGRA, kCVPixelFormatType_32BGRA},
+    {PJMEDIA_FORMAT_YUY2, kCVPixelFormatType_422YpCbCr8_yuvs},
+    {PJMEDIA_FORMAT_UYVY, kCVPixelFormatType_422YpCbCr8},
+};
+
+/* avf device info */
+struct avf_dev_info
+{
+    pjmedia_vid_dev_info	 info;
+    AVCaptureDevice              *dev;
+};
+
+/* avf factory */
+struct avf_factory
+{
+    pjmedia_vid_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_t			*dev_pool;
+    pj_pool_factory		*pf;
+
+    unsigned			 dev_count;
+    struct avf_dev_info		*dev_info;
+};
+
+struct avf_stream;    /* forward declaration */
+typedef void (*func_ptr)(struct avf_stream *strm);
+
+@interface AVFDelegate: NSObject <AVCaptureVideoDataOutputSampleBufferDelegate>
+{
+@public
+    struct avf_stream *stream;
+}
+@end
+
+
+/* Video stream. */
+struct avf_stream
+{
+    pjmedia_vid_dev_stream  base;	    /**< Base stream	       */
+    pjmedia_vid_dev_param   param;	    /**< Settings	       */
+    pj_pool_t		   *pool;           /**< Memory pool.          */
+
+    pj_timestamp	    cap_frame_ts;   /**< Captured frame tstamp */
+    unsigned		    cap_ts_inc;	    /**< Increment	       */
+
+    pjmedia_vid_dev_cb	    vid_cb;         /**< Stream callback.      */
+    void		   *user_data;      /**< Application data.     */
+
+    pjmedia_rect_size	    size;
+
+    pj_bool_t		    cap_thread_initialized;
+    pj_thread_desc	    cap_thread_desc;
+    pj_thread_t		   *cap_thread;
+    pj_bool_t               cap_exited;
+
+    struct avf_factory      *af;
+    pj_status_t             status;
+    pj_bool_t               is_running;
+
+    dispatch_queue_t                    video_ops_queue;
+
+    AVCaptureSession			*cap_session;
+    AVCaptureDeviceInput		*dev_input;
+    AVCaptureVideoDataOutput	        *video_output;
+    AVFDelegate                         *delegate;
+};
+
+
+/* Prototypes */
+static pj_status_t avf_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t avf_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t avf_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned    avf_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t avf_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					   unsigned index,
+					   pjmedia_vid_dev_info *info);
+static pj_status_t avf_factory_default_param(pj_pool_t *pool,
+					    pjmedia_vid_dev_factory *f,
+					    unsigned index,
+					    pjmedia_vid_dev_param *param);
+static pj_status_t avf_factory_create_stream(pjmedia_vid_dev_factory *f,
+					     pjmedia_vid_dev_param *param,
+					     const pjmedia_vid_dev_cb *cb,
+					     void *user_data,
+					     pjmedia_vid_dev_stream **p_vid_strm);
+
+static pj_status_t avf_stream_get_param(pjmedia_vid_dev_stream *strm,
+				        pjmedia_vid_dev_param *param);
+static pj_status_t avf_stream_get_cap(pjmedia_vid_dev_stream *strm,
+				      pjmedia_vid_dev_cap cap,
+				      void *value);
+static pj_status_t avf_stream_set_cap(pjmedia_vid_dev_stream *strm,
+				      pjmedia_vid_dev_cap cap,
+				      const void *value);
+static pj_status_t avf_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t avf_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t avf_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+    &avf_factory_init,
+    &avf_factory_destroy,
+    &avf_factory_get_dev_count,
+    &avf_factory_get_dev_info,
+    &avf_factory_default_param,
+    &avf_factory_create_stream,
+    &avf_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+    &avf_stream_get_param,
+    &avf_stream_get_cap,
+    &avf_stream_set_cap,
+    &avf_stream_start,
+    NULL,
+    NULL,
+    &avf_stream_stop,
+    &avf_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init avf video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_avf_factory(pj_pool_factory *pf)
+{
+    struct avf_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "avf video", 4000, 4000, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct avf_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t avf_factory_init(pjmedia_vid_dev_factory *f)
+{
+    return avf_factory_refresh(f);
+}
+
+/* API: destroy factory */
+static pj_status_t avf_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+    struct avf_factory *af = (struct avf_factory*)f;
+    pj_pool_t *pool = af->pool;
+
+    if (af->dev_pool)
+        pj_pool_release(af->dev_pool);
+    af->pool = NULL;
+    if (pool)
+        pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t avf_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+    struct avf_factory *af = (struct avf_factory*)f;
+    struct avf_dev_info *di;
+    unsigned dev_count = 0;
+    NSAutoreleasePool *apool = [[NSAutoreleasePool alloc]init];
+    NSArray *dev_array;
+
+    if (af->dev_pool) {
+        pj_pool_release(af->dev_pool);
+        af->dev_pool = NULL;
+    }
+
+    dev_array = [AVCaptureDevice devices];
+    for (AVCaptureDevice *device in dev_array) {
+	if ([device hasMediaType:AVMediaTypeVideo] && ![device isSuspended]) {
+	    dev_count++;
+	}
+    }
+
+    /* Initialize input and output devices here */
+    af->dev_count = 0;
+    af->dev_pool = pj_pool_create(af->pf, "avf video", 500, 500, NULL);
+
+    af->dev_info = (struct avf_dev_info*) pj_pool_calloc(af->dev_pool, dev_count, sizeof(struct avf_dev_info));
+    for (AVCaptureDevice *device in dev_array) {
+	if (![device hasMediaType:AVMediaTypeVideo] || [device isSuspended]) {
+	    continue;
+	}
+
+        di = &af->dev_info[af->dev_count++];
+        pj_bzero(di, sizeof(*di));
+        di->dev = device;
+        pj_ansi_strncpy(di->info.name, [device.localizedName UTF8String], sizeof(di->info.name));
+        pj_ansi_strncpy(di->info.driver, "AVF", sizeof(di->info.driver));
+        di->info.dir = PJMEDIA_DIR_CAPTURE;
+        di->info.has_callback = PJ_TRUE;
+        di->info.fmt_cnt = 0;
+        di->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+
+        PJ_LOG(4, (THIS_FILE, " dev: %s", di->info.name));
+
+        for (AVCaptureDeviceFormat* f in [device formats]) {
+            unsigned i;
+            CMFormatDescriptionRef desc = [f formatDescription];
+            for (i = 0; i < PJ_ARRAY_SIZE(avf_fmts); i++) {
+                if (CMFormatDescriptionGetMediaSubType(desc) == avf_fmts[i].avf_format) {
+                    char fmt_name[5];
+                    CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(desc);
+                    if (dim.width < 640)
+                        continue;
+                    pjmedia_fourcc_name(avf_fmts[i].pjmedia_format, fmt_name);
+                    PJ_LOG(4, (THIS_FILE, "  detected resolution %dx%d (%s)", dim.width, dim.height, fmt_name));
+                    pjmedia_format *fmt = &di->info.fmt[di->info.fmt_cnt++];
+                    pjmedia_format_init_video(fmt,
+                                              avf_fmts[i].pjmedia_format,
+                                              dim.width,
+                                              dim.height,
+                                              DEFAULT_FPS, 1);
+                }
+            }
+        }
+
+        if (di->info.fmt_cnt == 0) {
+            PJ_LOG(4, (THIS_FILE, "  there are no compatible formats, using default"));
+            pjmedia_format *fmt = &di->info.fmt[di->info.fmt_cnt++];
+            pjmedia_format_init_video(fmt,
+                                      avf_fmts[0].pjmedia_format,
+                                      DEFAULT_WIDTH,
+                                      DEFAULT_HEIGHT,
+                                      DEFAULT_FPS, 1);
+        }
+    }
+
+    [apool release];
+
+    PJ_LOG(4, (THIS_FILE, "avf video has %d devices", af->dev_count));
+
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned avf_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+    struct avf_factory *af = (struct avf_factory*)f;
+    return af->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t avf_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					    unsigned index,
+					    pjmedia_vid_dev_info *info)
+{
+    struct avf_factory *af = (struct avf_factory*)f;
+    PJ_ASSERT_RETURN(index < af->dev_count, PJMEDIA_EVID_INVDEV);
+
+    pj_memcpy(info, &af->dev_info[index].info, sizeof(*info));
+
+    return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t avf_factory_default_param(pj_pool_t *pool,
+					     pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_param *param)
+{
+    struct avf_factory *af = (struct avf_factory*)f;
+    struct avf_dev_info *di = &af->dev_info[index];
+
+    PJ_ASSERT_RETURN(index < af->dev_count, PJMEDIA_EVID_INVDEV);
+    PJ_UNUSED_ARG(pool);
+
+    pj_bzero(param, sizeof(*param));
+    param->dir = PJMEDIA_DIR_CAPTURE;
+    param->cap_id = index;
+    param->rend_id = PJMEDIA_VID_INVALID_DEV;
+    param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+    param->clock_rate = DEFAULT_CLOCK_RATE;
+    pj_memcpy(&param->fmt, &di->info.fmt[0], sizeof(param->fmt));
+
+    return PJ_SUCCESS;
+}
+
+static avf_fmt_info* get_avf_format_info(pjmedia_format_id id)
+{
+    unsigned i;
+
+    for (i = 0; i < PJ_ARRAY_SIZE(avf_fmts); i++) {
+        if (avf_fmts[i].pjmedia_format == id)
+            return &avf_fmts[i];
+    }
+
+    return NULL;
+}
+
+
+@implementation AVFDelegate
+- (void)captureOutput:(AVCaptureOutput *)captureOutput
+		      didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
+		      fromConnection:(AVCaptureConnection *)connection
+{
+    pjmedia_frame frame = {0};
+    CVImageBufferRef img;
+    CVReturn ret;
+    OSType type;
+    size_t width, height;
+
+    /* Register thread if needed */
+    if (stream->cap_thread_initialized == 0 || !pj_thread_is_registered()) {
+        pj_bzero(stream->cap_thread_desc, sizeof(pj_thread_desc));
+        pj_thread_register("avf_cap", stream->cap_thread_desc, &stream->cap_thread);
+        stream->cap_thread_initialized = 1;
+    }
+
+    if (!sampleBuffer)
+	return;
+
+    /* Get a CMSampleBuffer's Core Video image buffer for the media data */
+    img = CMSampleBufferGetImageBuffer(sampleBuffer);
+    if (!img)
+        return;
+
+    /* Check for supported formats */
+    type = CVPixelBufferGetPixelFormatType(img);
+    switch(type) {
+        case kCVPixelFormatType_32BGRA:
+        case kCVPixelFormatType_422YpCbCr8_yuvs:
+        case kCVPixelFormatType_422YpCbCr8:
+            break;
+        default:
+            PJ_LOG(4, (THIS_FILE, "Unsupported image format! %c%c%c%c", type>>24, type>>16, type>>8, type>>0));
+            return;
+    }
+
+    /* Lock the base address of the pixel buffer */
+    ret = CVPixelBufferLockBaseAddress(img, kCVPixelBufferLock_ReadOnly);
+    if (ret != kCVReturnSuccess)
+        return;
+
+    width = CVPixelBufferGetWidth(img);
+    height = CVPixelBufferGetHeight(img);
+
+    /* Prepare frame */
+    frame.type = PJMEDIA_FRAME_TYPE_VIDEO;
+    frame.timestamp.u64 = stream->cap_frame_ts.u64;
+    frame.buf = CVPixelBufferGetBaseAddress(img);
+    frame.size = CVPixelBufferGetBytesPerRow(img) * height;
+
+    if (stream->size.w != width || stream->size.h != height) {
+        PJ_LOG(4, (THIS_FILE, "AVF image size changed, before: %dx%d, after: %dx%d", stream->size.w, stream->size.h, width, height));
+    }
+
+    if (stream->vid_cb.capture_cb) {
+        (*stream->vid_cb.capture_cb)(&stream->base, stream->user_data, &frame);
+    }
+
+    stream->cap_frame_ts.u64 += stream->cap_ts_inc;
+
+    /* Unlock the pixel buffer */
+    CVPixelBufferUnlockBaseAddress(img, kCVPixelBufferLock_ReadOnly);
+}
+@end
+
+
+static void init_avf_stream(struct avf_stream *strm)
+{
+    const pjmedia_video_format_info *vfi;
+    pjmedia_video_format_detail *vfd;
+    avf_fmt_info *fi = get_avf_format_info(strm->param.fmt.id);
+    NSError *error;
+    pj_status_t status;
+
+    if (!fi) {
+        strm->status = PJMEDIA_EVID_BADFORMAT;
+        return;
+    }
+
+    strm->cap_session = [[AVCaptureSession alloc] init];
+    if (!strm->cap_session) {
+        strm->status = PJ_ENOMEM;
+        return;
+    }
+
+    strm->cap_session.sessionPreset = AVCaptureSessionPresetHigh;
+    vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE);
+    pj_assert(vfd);
+    vfi = pjmedia_get_video_format_info(NULL, strm->param.fmt.id);
+    pj_assert(vfi);
+    vfd->size = strm->size;
+
+    PJ_LOG(4, (THIS_FILE, "Opening video device at %dx%d resolution", vfd->size.w, vfd->size.h));
+
+    /* Add the video device to the session as a device input */
+    AVCaptureDevice *videoDevice = strm->af->dev_info[strm->param.cap_id].dev;
+    strm->dev_input = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error: &error];
+    if (!strm->dev_input) {
+        status = PJMEDIA_EVID_SYSERR;
+        return;
+    }
+
+    [strm->cap_session addInput:strm->dev_input];
+
+    strm->video_output = [[AVCaptureVideoDataOutput alloc] init];
+    if (!strm->video_output) {
+        status = PJMEDIA_EVID_SYSERR;
+        return;
+    }
+    [strm->cap_session addOutput:strm->video_output];
+
+    /* Configure the video output */
+    strm->video_output.alwaysDiscardsLateVideoFrames = YES;
+    /* The Apple provided documentation says the only supported key is kCVPixelBufferPixelFormatTypeKey,
+     * but it turns out kCVPixelBufferWidthKey and kCVPixelBufferHeightKey are also required. Thanks
+     * Chromium, for figuring it out.*/
+    strm->video_output.videoSettings =
+        [NSDictionary dictionaryWithObjectsAndKeys: @(fi->avf_format),
+                                                    kCVPixelBufferPixelFormatTypeKey,
+                                                    @(vfd->size.w),
+                                                    kCVPixelBufferWidthKey,
+                                                    @(vfd->size.h),
+                                                    kCVPixelBufferHeightKey,
+                                                    nil];
+    strm->delegate = [[AVFDelegate alloc] init];
+    strm->delegate->stream = strm;
+    dispatch_queue_t queue = dispatch_queue_create("AVFQueue", NULL);
+    [strm->video_output setSampleBufferDelegate:strm->delegate queue:queue];
+    dispatch_release(queue);
+}
+
+static void run_func_on_video_queue(struct avf_stream *strm, func_ptr func)
+{
+    dispatch_sync(strm->video_ops_queue, ^{
+        (*func)(strm);
+    });
+}
+
+/* API: create stream */
+static pj_status_t avf_factory_create_stream(pjmedia_vid_dev_factory *f,
+					     pjmedia_vid_dev_param *param,
+					     const pjmedia_vid_dev_cb *cb,
+					     void *user_data,
+					     pjmedia_vid_dev_stream **p_vid_strm)
+{
+    struct avf_factory *af = (struct avf_factory*)f;
+    pj_pool_t *pool;
+    struct avf_stream *strm;
+    const pjmedia_video_format_info *vfi;
+    pjmedia_video_format_detail *vfd;
+    pj_status_t status = PJ_SUCCESS;
+
+    PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
+    PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO &&
+		     param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO &&
+                     param->dir == PJMEDIA_DIR_CAPTURE,
+		     PJ_EINVAL);
+
+    vfi = pjmedia_get_video_format_info(NULL, param->fmt.id);
+    if (!vfi)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(af->pf, "avf-dev", 4000, 4000, NULL);
+    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct avf_stream);
+    pj_memcpy(&strm->param, param, sizeof(*param));
+    strm->pool = pool;
+    pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+    strm->user_data = user_data;
+    strm->af = af;
+
+    vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE);
+    pj_memcpy(&strm->size, &vfd->size, sizeof(vfd->size));
+    pj_assert(vfd->fps.num);
+    strm->cap_ts_inc = PJMEDIA_SPF2(strm->param.clock_rate, &vfd->fps, 1);
+
+    /* Create dispatch queue */
+    strm->video_ops_queue = dispatch_queue_create("AVF Video Ops", DISPATCH_QUEUE_SERIAL);
+
+    /* Create capture stream here */
+    strm->status = PJ_SUCCESS;
+    run_func_on_video_queue(strm, init_avf_stream);
+    status = strm->status;
+    if (status != PJ_SUCCESS) {
+        dispatch_release(strm->video_ops_queue);
+        avf_stream_destroy((pjmedia_vid_dev_stream *)strm);
+        return status;
+    }
+
+    /* Update param as output */
+    param->fmt = strm->param.fmt;
+
+    /* Done */
+    strm->base.op = &stream_op;
+    *p_vid_strm = &strm->base;
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t avf_stream_get_param(pjmedia_vid_dev_stream *s,
+				        pjmedia_vid_dev_param *pi)
+{
+    struct avf_stream *strm = (struct avf_stream*)s;
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t avf_stream_get_cap(pjmedia_vid_dev_stream *s,
+				      pjmedia_vid_dev_cap cap,
+				      void *pval)
+{
+    struct avf_stream *strm = (struct avf_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+    PJ_UNUSED_ARG(cap);
+    PJ_UNUSED_ARG(pval);
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: set capability */
+static pj_status_t avf_stream_set_cap(pjmedia_vid_dev_stream *s,
+				     pjmedia_vid_dev_cap cap,
+				     const void *pval)
+{
+    struct avf_stream *strm = (struct avf_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+    PJ_UNUSED_ARG(cap);
+    PJ_UNUSED_ARG(pval);
+
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+static void start_avf(struct avf_stream *strm)
+{
+    [strm->cap_session startRunning];
+}
+
+static void stop_avf(struct avf_stream *strm)
+{
+    [strm->cap_session stopRunning];
+}
+
+/* API: Start stream. */
+static pj_status_t avf_stream_start(pjmedia_vid_dev_stream *strm)
+{
+    struct avf_stream *stream = (struct avf_stream*)strm;
+
+    PJ_LOG(4, (THIS_FILE, "Starting avf video stream"));
+
+    if (stream->cap_session) {
+        run_func_on_video_queue(stream, start_avf);
+	if (![stream->cap_session isRunning])
+	    return PJMEDIA_EVID_NOTREADY;
+        stream->is_running = PJ_TRUE;
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t avf_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+    struct avf_stream *stream = (struct avf_stream*)strm;
+
+    PJ_LOG(4, (THIS_FILE, "Stopping avf video stream"));
+
+    if (stream->cap_session && [stream->cap_session isRunning]) {
+        int i;
+        stream->cap_exited = PJ_FALSE;
+        run_func_on_video_queue(stream, stop_avf);
+        stream->is_running = PJ_FALSE;
+        for (i = 50; i >= 0 && !stream->cap_exited; i--) {
+            pj_thread_sleep(10);
+        }
+    }
+
+    return PJ_SUCCESS;
+}
+
+static void destroy_avf(struct avf_stream *strm)
+{
+    if (strm->cap_session) {
+        [strm->cap_session removeInput:strm->dev_input];
+        [strm->cap_session removeOutput:strm->video_output];
+	[strm->cap_session release];
+	strm->cap_session = NULL;
+    }
+
+    if (strm->delegate) {
+	[strm->delegate release];
+	strm->delegate = NULL;
+    }
+
+    if (strm->dev_input) {
+	strm->dev_input = NULL;
+    }
+    if (strm->video_output) {
+	strm->video_output = NULL;
+    }
+}
+
+/* API: Destroy stream. */
+static pj_status_t avf_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+    struct avf_stream *stream = (struct avf_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+    avf_stream_stop(strm);
+    run_func_on_video_queue(stream, destroy_avf);
+
+    dispatch_release(stream->video_ops_queue);
+    pj_pool_release(stream->pool);
+
+    return PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_AVF */
diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_filter.cpp pjsip/pjmedia/src/pjmedia-videodev/dshow_filter.cpp
--- pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_filter.cpp	1970-01-01 01:00:00.000000000 +0100
+++ pjsip/pjmedia/src/pjmedia-videodev/dshow_filter.cpp	2021-02-06 18:47:09.030177557 +0100
@@ -0,0 +1,83 @@
+/* $Id: dshowclasses.cpp 4062 2012-04-19 06:36:57Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <pjmedia-videodev/config.h>
+
+
+#if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0
+
+#include <DShow.h>
+#include <assert.h>
+#include <streams.h>
+
+typedef void (*input_callback)(void *user_data, IMediaSample *pMediaSample);
+
+const GUID CLSID_NullRenderer = {0xF9168C5E, 0xCEB2, 0x4FAA, {0xB6, 0xBF,
+                                 0x32, 0x9B, 0xF3, 0x9F, 0xA1, 0xE4}};
+
+class NullRenderer: public CBaseRenderer
+{
+public:
+    NullRenderer(HRESULT *pHr);
+    virtual ~NullRenderer();
+
+    virtual HRESULT CheckMediaType(const CMediaType *pmt);
+    virtual HRESULT DoRenderSample(IMediaSample *pMediaSample);
+
+    input_callback  input_cb;
+    void           *user_data;
+};
+
+NullRenderer::NullRenderer(HRESULT *pHr): CBaseRenderer(CLSID_NullRenderer,
+                                                        "NullRenderer",
+                                                        NULL, pHr)
+{
+    input_cb = NULL;
+}
+
+NullRenderer::~NullRenderer()
+{
+}
+
+HRESULT NullRenderer::CheckMediaType(const CMediaType *pmt)
+{
+    return S_OK;
+}
+
+HRESULT NullRenderer::DoRenderSample(IMediaSample *pMediaSample)
+{
+    if (input_cb)
+        input_cb(user_data, pMediaSample);
+
+    return S_OK;
+}
+
+extern "C" IBaseFilter* NullRenderer_Create(input_callback input_cb,
+                                             void *user_data)
+{
+    HRESULT hr;
+    NullRenderer *renderer = new NullRenderer(&hr);
+    renderer->AddRef();
+    renderer->input_cb = input_cb;
+    renderer->user_data = user_data;
+
+    return (CBaseFilter *)renderer;
+}
+
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_DSHOW */
diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/fb_dev.c pjsip/pjmedia/src/pjmedia-videodev/fb_dev.c
--- pjproject-2.10/pjmedia/src/pjmedia-videodev/fb_dev.c	1970-01-01 01:00:00.000000000 +0100
+++ pjsip/pjmedia/src/pjmedia-videodev/fb_dev.c	2021-02-06 18:47:25.010632996 +0100
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2014-present AG Projects
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <pjmedia-videodev/videodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \
+    defined(PJMEDIA_VIDEO_DEV_HAS_FB) && PJMEDIA_VIDEO_DEV_HAS_FB != 0
+
+#include <pjmedia-videodev/fb_dev.h>
+
+#define THIS_FILE		"fb_dev.c"
+#define DEFAULT_CLOCK_RATE	90000
+#define DEFAULT_WIDTH		640
+#define DEFAULT_HEIGHT		480
+#define DEFAULT_FPS		25
+
+
+/* Supported formats */
+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
+static pjmedia_format_id fb_fmts[] = {PJMEDIA_FORMAT_ARGB};
+#else
+static pjmedia_format_id fb_fmts[] = {PJMEDIA_FORMAT_BGRA};
+#endif
+
+
+/* fb device info */
+struct fb_dev_info
+{
+    pjmedia_vid_dev_info	 info;
+};
+
+
+/* factory */
+struct fb_factory
+{
+    pjmedia_vid_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_factory		*pf;
+
+    unsigned			 dev_count;
+    struct fb_dev_info	        *dev_info;
+};
+
+
+/* Video stream. */
+struct fb_stream
+{
+    pjmedia_vid_dev_stream	 base;		    /**< Base stream	    */
+    pjmedia_vid_dev_param	 param;		    /**< Settings	    */
+    pj_pool_t			*pool;              /**< Memory pool.       */
+
+    pjmedia_vid_dev_cb		 vid_cb;            /**< Stream callback.   */
+    void			*user_data;         /**< Application data.  */
+
+    struct fb_factory           *ff;
+    pj_bool_t			 is_running;
+    pjmedia_rect_size            vid_size;
+
+    struct {
+        pjmedia_vid_dev_fb_frame_cb cb;
+        void *user_data;
+    } frame_handler;
+};
+
+
+/* Prototypes */
+static pj_status_t fb_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t fb_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t fb_factory_refresh(pjmedia_vid_dev_factory *f);
+static unsigned    fb_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t fb_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					   unsigned index,
+					   pjmedia_vid_dev_info *info);
+static pj_status_t fb_factory_default_param(pj_pool_t *pool,
+                                            pjmedia_vid_dev_factory *f,
+					    unsigned index,
+					    pjmedia_vid_dev_param *param);
+static pj_status_t fb_factory_create_stream(pjmedia_vid_dev_factory *f,
+					    pjmedia_vid_dev_param *param,
+					    const pjmedia_vid_dev_cb *cb,
+					    void *user_data,
+					    pjmedia_vid_dev_stream **p_vid_strm);
+
+static pj_status_t fb_stream_get_param(pjmedia_vid_dev_stream *strm,
+				       pjmedia_vid_dev_param *param);
+static pj_status_t fb_stream_get_cap(pjmedia_vid_dev_stream *strm,
+				     pjmedia_vid_dev_cap cap,
+				     void *value);
+static pj_status_t fb_stream_set_cap(pjmedia_vid_dev_stream *strm,
+				     pjmedia_vid_dev_cap cap,
+				     const void *value);
+static pj_status_t fb_stream_put_frame(pjmedia_vid_dev_stream *strm,
+                                       const pjmedia_frame *frame);
+static pj_status_t fb_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t fb_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t fb_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+    &fb_factory_init,
+    &fb_factory_destroy,
+    &fb_factory_get_dev_count,
+    &fb_factory_get_dev_info,
+    &fb_factory_default_param,
+    &fb_factory_create_stream,
+    &fb_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+    &fb_stream_get_param,
+    &fb_stream_get_cap,
+    &fb_stream_set_cap,
+    &fb_stream_start,
+    NULL,
+    &fb_stream_put_frame,
+    &fb_stream_stop,
+    &fb_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init FB video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_fb_factory(pj_pool_factory *pf)
+{
+    struct fb_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "fb video", 1000, 1000, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct fb_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t fb_factory_init(pjmedia_vid_dev_factory *f)
+{
+    struct fb_factory *ff = (struct fb_factory*)f;
+    struct fb_dev_info *di;
+    unsigned i, l;
+
+    /* Initialize input and output devices here */
+    ff->dev_info = (struct fb_dev_info*)
+    pj_pool_calloc(ff->pool, 1, sizeof(struct fb_dev_info));
+
+    ff->dev_count = 0;
+    di = &ff->dev_info[ff->dev_count++];
+    pj_bzero(di, sizeof(*di));
+    strcpy(di->info.name, "FrameBuffer renderer");
+    strcpy(di->info.driver, "FrameBuffer");
+    di->info.dir = PJMEDIA_DIR_RENDER;
+    di->info.has_callback = PJ_FALSE;
+    di->info.caps = 0;
+
+    for (i = 0; i < ff->dev_count; i++) {
+	di = &ff->dev_info[i];
+	di->info.fmt_cnt = PJ_ARRAY_SIZE(fb_fmts);
+	di->info.caps |= PJMEDIA_VID_DEV_CAP_FORMAT;
+
+	for (l = 0; l < PJ_ARRAY_SIZE(fb_fmts); l++) {
+	    pjmedia_format *fmt = &di->info.fmt[l];
+	    pjmedia_format_init_video(fmt,
+				      fb_fmts[l],
+				      DEFAULT_WIDTH,
+				      DEFAULT_HEIGHT,
+				      DEFAULT_FPS, 1);
+	}
+    }
+
+    PJ_LOG(4, (THIS_FILE, "FrameBuffer initialized"));
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: destroy factory */
+static pj_status_t fb_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+    struct fb_factory *ff = (struct fb_factory*)f;
+    pj_pool_t *pool = ff->pool;
+
+    ff->pool = NULL;
+    pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: refresh the list of devices */
+static pj_status_t fb_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    return PJ_SUCCESS;
+}
+
+
+/* API: get number of devices */
+static unsigned fb_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+    struct fb_factory *ff = (struct fb_factory*)f;
+    return ff->dev_count;
+}
+
+
+/* API: get device info */
+static pj_status_t fb_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					   unsigned index,
+					   pjmedia_vid_dev_info *info)
+{
+    struct fb_factory *ff = (struct fb_factory*)f;
+
+    PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV);
+    pj_memcpy(info, &ff->dev_info[index].info, sizeof(*info));
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: create default device parameter */
+static pj_status_t fb_factory_default_param(pj_pool_t *pool,
+                                            pjmedia_vid_dev_factory *f,
+					    unsigned index,
+					    pjmedia_vid_dev_param *param)
+{
+    struct fb_factory *ff = (struct fb_factory*)f;
+    struct fb_dev_info *di = &ff->dev_info[index];
+
+    PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV);
+    PJ_UNUSED_ARG(pool);
+
+    pj_bzero(param, sizeof(*param));
+    param->dir = PJMEDIA_DIR_RENDER;
+    param->rend_id = index;
+    param->cap_id = PJMEDIA_VID_INVALID_DEV;
+
+    /* Set the device capabilities here */
+    param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+    param->clock_rate = DEFAULT_CLOCK_RATE;
+    pj_memcpy(&param->fmt, &di->info.fmt[0], sizeof(param->fmt));
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Put frame from stream */
+static pj_status_t fb_stream_put_frame(pjmedia_vid_dev_stream *strm,
+                                       const pjmedia_frame *frame)
+{
+    struct fb_stream *stream = (struct fb_stream*)strm;
+
+    if (!stream->is_running)
+	return PJ_EINVALIDOP;
+
+    if (frame->size==0 || frame->buf==NULL)
+	return PJ_SUCCESS;
+
+    if (stream->frame_handler.cb)
+        stream->frame_handler.cb(frame, stream->vid_size, stream->frame_handler.user_data);
+
+    return PJ_SUCCESS;
+}
+
+/* API: create stream */
+static pj_status_t fb_factory_create_stream(pjmedia_vid_dev_factory *f,
+					    pjmedia_vid_dev_param *param,
+					    const pjmedia_vid_dev_cb *cb,
+					    void *user_data,
+					    pjmedia_vid_dev_stream **p_vid_strm)
+{
+    struct fb_factory *ff = (struct fb_factory*)f;
+    pj_pool_t *pool;
+    pj_status_t status;
+    struct fb_stream *strm;
+    const pjmedia_video_format_info *vfi;
+
+    PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
+    PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL);
+    PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO &&
+		     param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO &&
+                     param->dir == PJMEDIA_DIR_RENDER,
+		     PJ_EINVAL);
+
+    vfi = pjmedia_get_video_format_info(NULL, param->fmt.id);
+    if (!vfi)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(ff->pf, "fb-dev", 1000, 1000, NULL);
+    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct fb_stream);
+    pj_memcpy(&strm->param, param, sizeof(*param));
+    strm->pool = pool;
+    strm->ff = ff;
+    pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+    strm->user_data = user_data;
+
+    status = fb_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_FORMAT, &param->fmt);
+    if (status != PJ_SUCCESS) {
+        fb_stream_destroy((pjmedia_vid_dev_stream *)strm);
+        return status;
+    }
+
+    /* Done */
+    strm->base.op = &stream_op;
+    *p_vid_strm = &strm->base;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Get stream info. */
+static pj_status_t fb_stream_get_param(pjmedia_vid_dev_stream *s,
+			               pjmedia_vid_dev_param *pi)
+{
+    struct fb_stream *strm = (struct fb_stream*)s;
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: get capability */
+static pj_status_t fb_stream_get_cap(pjmedia_vid_dev_stream *s,
+				     pjmedia_vid_dev_cap cap,
+				     void *pval)
+{
+    struct fb_stream *strm = (struct fb_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+    PJ_UNUSED_ARG(cap);
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+
+/* API: set capability */
+static pj_status_t fb_stream_set_cap(pjmedia_vid_dev_stream *s,
+				     pjmedia_vid_dev_cap cap,
+				     const void *pval)
+{
+    struct fb_stream *strm = (struct fb_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+
+    if (cap == PJMEDIA_VID_DEV_CAP_FORMAT) {
+        const pjmedia_video_format_info *vfi;
+        pjmedia_video_format_detail *vfd;
+        pjmedia_format *fmt = (pjmedia_format *)pval;
+
+        vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), fmt->id);
+        if (!vfi)
+            return PJMEDIA_EVID_BADFORMAT;
+
+        pjmedia_format_copy(&strm->param.fmt, fmt);
+
+        vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE);
+        pj_memcpy(&strm->vid_size, &vfd->size, sizeof(vfd->size));
+        if (strm->param.disp_size.w == 0 || strm->param.disp_size.h == 0)
+            pj_memcpy(&strm->param.disp_size, &vfd->size, sizeof(vfd->size));
+
+        return PJ_SUCCESS;
+    }
+
+    return PJMEDIA_EVID_INVCAP;
+}
+
+
+/* API: Start stream. */
+static pj_status_t fb_stream_start(pjmedia_vid_dev_stream *strm)
+{
+    struct fb_stream *stream = (struct fb_stream*)strm;
+    PJ_UNUSED_ARG(strm);
+
+    PJ_LOG(4, (THIS_FILE, "Starting FB video stream"));
+    stream->is_running = PJ_TRUE;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Stop stream. */
+static pj_status_t fb_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+    struct fb_stream *stream = (struct fb_stream*)strm;
+    PJ_UNUSED_ARG(strm);
+
+    PJ_LOG(4, (THIS_FILE, "Stopping FB video stream"));
+    stream->is_running = PJ_FALSE;
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t fb_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+    struct fb_stream *stream = (struct fb_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+    fb_stream_stop(strm);
+    pj_pool_release(stream->pool);
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: set callback for handling frames */
+pj_status_t
+pjmedia_vid_dev_fb_set_callback(pjmedia_vid_dev_stream *strm,
+                                pjmedia_vid_dev_fb_frame_cb cb,
+                                void *user_data)
+{
+    struct fb_stream *stream = (struct fb_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+    if (stream->is_running)
+        return PJ_EBUSY;
+
+    stream->frame_handler.cb = cb;
+    stream->frame_handler.user_data = user_data;
+
+    return PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_FB */
diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/null_dev.c pjsip/pjmedia/src/pjmedia-videodev/null_dev.c
--- pjproject-2.10/pjmedia/src/pjmedia-videodev/null_dev.c	1970-01-01 01:00:00.000000000 +0100
+++ pjsip/pjmedia/src/pjmedia-videodev/null_dev.c	2021-02-06 18:47:47.343269399 +0100
@@ -0,0 +1,440 @@
+/* $Id: colorbar_dev.c 4158 2012-06-06 09:56:14Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <pjmedia-videodev/videodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+
+#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \
+    defined(PJMEDIA_VIDEO_DEV_HAS_NULL) && \
+    PJMEDIA_VIDEO_DEV_HAS_NULL != 0
+
+
+#define THIS_FILE		"null_dev.c"
+#define DEFAULT_CLOCK_RATE	90000
+#define DEFAULT_WIDTH		640
+#define DEFAULT_HEIGHT		480
+#define DEFAULT_FPS		5
+
+/* null_ device info */
+struct null_dev_info
+{
+    pjmedia_vid_dev_info	 info;
+};
+
+/* null_ factory */
+struct null_factory
+{
+    pjmedia_vid_dev_factory	 base;
+    pj_pool_t			*pool;
+    pj_pool_factory		*pf;
+
+    unsigned			 dev_count;
+    struct null_dev_info	*dev_info;
+};
+
+struct null_fmt_info {
+    pjmedia_format_id            fmt_id;        /* Format ID                */
+};
+
+/* Null video source supports */
+static struct null_fmt_info null_fmts[] =
+{
+    { PJMEDIA_FORMAT_BGRA },
+};
+
+/* Video stream. */
+struct null_stream
+{
+    pjmedia_vid_dev_stream	     base;	    /**< Base stream	    */
+    pjmedia_vid_dev_param	     param;	    /**< Settings	    */
+    pj_pool_t			    *pool;          /**< Memory pool.       */
+
+    pjmedia_vid_dev_cb		     vid_cb;	    /**< Stream callback.   */
+    void			    *user_data;	    /**< Application data.  */
+
+    const struct null_fmt_info      *cbfi;
+    const pjmedia_video_format_info *vfi;
+    pjmedia_video_apply_fmt_param    vafp;
+    pj_uint8_t                      *first_line[PJMEDIA_MAX_VIDEO_PLANES];
+    pj_timestamp		     ts;
+    unsigned			     ts_inc;
+};
+
+
+/* Prototypes */
+static pj_status_t null_factory_init(pjmedia_vid_dev_factory *f);
+static pj_status_t null_factory_destroy(pjmedia_vid_dev_factory *f);
+static pj_status_t null_factory_refresh(pjmedia_vid_dev_factory *f); 
+static unsigned    null_factory_get_dev_count(pjmedia_vid_dev_factory *f);
+static pj_status_t null_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_info *info);
+static pj_status_t null_factory_default_param(pj_pool_t *pool,
+                                              pjmedia_vid_dev_factory *f,
+					      unsigned index,
+					      pjmedia_vid_dev_param *param);
+static pj_status_t null_factory_create_stream(
+					pjmedia_vid_dev_factory *f,
+					pjmedia_vid_dev_param *param,
+					const pjmedia_vid_dev_cb *cb,
+					void *user_data,
+					pjmedia_vid_dev_stream **p_vid_strm);
+
+static pj_status_t null_stream_get_param(pjmedia_vid_dev_stream *strm,
+					 pjmedia_vid_dev_param *param);
+static pj_status_t null_stream_get_cap(pjmedia_vid_dev_stream *strm,
+				       pjmedia_vid_dev_cap cap,
+				       void *value);
+static pj_status_t null_stream_set_cap(pjmedia_vid_dev_stream *strm,
+				       pjmedia_vid_dev_cap cap,
+				       const void *value);
+static pj_status_t null_stream_get_frame(pjmedia_vid_dev_stream *strm,
+                                         pjmedia_frame *frame);
+static pj_status_t null_stream_start(pjmedia_vid_dev_stream *strm);
+static pj_status_t null_stream_stop(pjmedia_vid_dev_stream *strm);
+static pj_status_t null_stream_destroy(pjmedia_vid_dev_stream *strm);
+
+/* Operations */
+static pjmedia_vid_dev_factory_op factory_op =
+{
+    &null_factory_init,
+    &null_factory_destroy,
+    &null_factory_get_dev_count,
+    &null_factory_get_dev_info,
+    &null_factory_default_param,
+    &null_factory_create_stream,
+    &null_factory_refresh
+};
+
+static pjmedia_vid_dev_stream_op stream_op =
+{
+    &null_stream_get_param,
+    &null_stream_get_cap,
+    &null_stream_set_cap,
+    &null_stream_start,
+    &null_stream_get_frame,
+    NULL,
+    &null_stream_stop,
+    &null_stream_destroy
+};
+
+
+/****************************************************************************
+ * Factory operations
+ */
+/*
+ * Init null_ video driver.
+ */
+pjmedia_vid_dev_factory* pjmedia_null_factory(pj_pool_factory *pf)
+{
+    struct null_factory *f;
+    pj_pool_t *pool;
+
+    pool = pj_pool_create(pf, "null video", 512, 512, NULL);
+    f = PJ_POOL_ZALLOC_T(pool, struct null_factory);
+    f->pf = pf;
+    f->pool = pool;
+    f->base.op = &factory_op;
+
+    return &f->base;
+}
+
+
+/* API: init factory */
+static pj_status_t null_factory_init(pjmedia_vid_dev_factory *f)
+{
+    struct null_factory *cf = (struct null_factory*)f;
+    struct null_dev_info *ddi;
+    unsigned i;
+
+    cf->dev_count = 1;
+    cf->dev_info = (struct null_dev_info*)
+ 		   pj_pool_calloc(cf->pool, cf->dev_count,
+ 				  sizeof(struct null_dev_info));
+
+    ddi = &cf->dev_info[0];
+    pj_bzero(ddi, sizeof(*ddi));
+    pj_ansi_strncpy(ddi->info.name, "Null video device",
+		    sizeof(ddi->info.name));
+    ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
+    pj_ansi_strncpy(ddi->info.driver, "Null", sizeof(ddi->info.driver));
+    ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
+    ddi->info.dir = PJMEDIA_DIR_CAPTURE;
+    ddi->info.has_callback = PJ_FALSE;
+
+    ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
+    ddi->info.fmt_cnt = sizeof(null_fmts)/sizeof(null_fmts[0]);
+    for (i = 0; i < ddi->info.fmt_cnt; i++) {
+        pjmedia_format *fmt = &ddi->info.fmt[i];
+        pjmedia_format_init_video(fmt, null_fmts[i].fmt_id,
+				  DEFAULT_WIDTH, DEFAULT_HEIGHT,
+				  DEFAULT_FPS, 1);
+    }
+
+    PJ_LOG(4, (THIS_FILE, "Null video src initialized with %d device(s):", cf->dev_count));
+    for (i = 0; i < cf->dev_count; i++) {
+	PJ_LOG(4, (THIS_FILE, "%2d: %s", i, cf->dev_info[i].info.name));
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: destroy factory */
+static pj_status_t null_factory_destroy(pjmedia_vid_dev_factory *f)
+{
+    struct null_factory *cf = (struct null_factory*)f;
+    pj_pool_t *pool = cf->pool;
+
+    cf->pool = NULL;
+    pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+/* API: refresh the list of devices */
+static pj_status_t null_factory_refresh(pjmedia_vid_dev_factory *f)
+{
+    PJ_UNUSED_ARG(f);
+    return PJ_SUCCESS;
+}
+
+/* API: get number of devices */
+static unsigned null_factory_get_dev_count(pjmedia_vid_dev_factory *f)
+{
+    struct null_factory *cf = (struct null_factory*)f;
+    return cf->dev_count;
+}
+
+/* API: get device info */
+static pj_status_t null_factory_get_dev_info(pjmedia_vid_dev_factory *f,
+					     unsigned index,
+					     pjmedia_vid_dev_info *info)
+{
+    struct null_factory *cf = (struct null_factory*)f;
+
+    PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
+
+    pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info));
+
+    return PJ_SUCCESS;
+}
+
+/* API: create default device parameter */
+static pj_status_t null_factory_default_param(pj_pool_t *pool,
+                                              pjmedia_vid_dev_factory *f,
+					      unsigned index,
+					      pjmedia_vid_dev_param *param)
+{
+    struct null_factory *cf = (struct null_factory*)f;
+    struct null_dev_info *di = &cf->dev_info[index];
+
+    PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
+
+    PJ_UNUSED_ARG(pool);
+
+    pj_bzero(param, sizeof(*param));
+    param->dir = PJMEDIA_DIR_CAPTURE;
+    param->cap_id = index;
+    param->rend_id = PJMEDIA_VID_INVALID_DEV;
+    param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
+    param->clock_rate = DEFAULT_CLOCK_RATE;
+    pj_memcpy(&param->fmt, &di->info.fmt[0], sizeof(param->fmt));
+
+    return PJ_SUCCESS;
+}
+
+static const struct null_fmt_info* get_null_fmt_info(pjmedia_format_id id)
+{
+    unsigned i;
+
+    for (i = 0; i < sizeof(null_fmts)/sizeof(null_fmts[0]); i++) {
+        if (null_fmts[i].fmt_id == id)
+            return &null_fmts[i];
+    }
+
+    return NULL;
+}
+
+
+/* API: create stream */
+static pj_status_t null_factory_create_stream(
+					pjmedia_vid_dev_factory *f,
+					pjmedia_vid_dev_param *param,
+					const pjmedia_vid_dev_cb *cb,
+					void *user_data,
+					pjmedia_vid_dev_stream **p_vid_strm)
+{
+    struct null_factory *cf = (struct null_factory*)f;
+    pj_pool_t *pool;
+    struct null_stream *strm;
+    const pjmedia_video_format_detail *vfd;
+    const pjmedia_video_format_info *vfi;
+    pjmedia_video_apply_fmt_param vafp;
+    const struct null_fmt_info *cbfi;
+    unsigned i;
+
+    PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
+    PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO &&
+		     param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO &&
+                     param->dir == PJMEDIA_DIR_CAPTURE,
+		     PJ_EINVAL);
+
+    pj_bzero(&vafp, sizeof(vafp));
+
+    vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
+    vfi = pjmedia_get_video_format_info(NULL, param->fmt.id);
+    cbfi = get_null_fmt_info(param->fmt.id);
+    if (!vfi || !cbfi)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    vafp.size = param->fmt.det.vid.size;
+    if (vfi->apply_fmt(vfi, &vafp) != PJ_SUCCESS)
+        return PJMEDIA_EVID_BADFORMAT;
+
+    /* Create and Initialize stream descriptor */
+    pool = pj_pool_create(cf->pf, "null-dev", 512, 512, NULL);
+    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+    strm = PJ_POOL_ZALLOC_T(pool, struct null_stream);
+    pj_memcpy(&strm->param, param, sizeof(*param));
+    strm->pool = pool;
+    pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
+    strm->user_data = user_data;
+    strm->vfi = vfi;
+    strm->cbfi = cbfi;
+    pj_memcpy(&strm->vafp, &vafp, sizeof(vafp));
+    strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1);
+
+    for (i = 0; i < vfi->plane_cnt; ++i) {
+        strm->first_line[i] = pj_pool_alloc(pool, vafp.strides[i]);
+        pj_memset(strm->first_line[i], 0, vafp.strides[i]);
+    }
+
+    /* Done */
+    strm->base.op = &stream_op;
+    *p_vid_strm = &strm->base;
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get stream info. */
+static pj_status_t null_stream_get_param(pjmedia_vid_dev_stream *s,
+					 pjmedia_vid_dev_param *pi)
+{
+    struct null_stream *strm = (struct null_stream*)s;
+
+    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
+
+    pj_memcpy(pi, &strm->param, sizeof(*pi));
+    return PJ_SUCCESS;
+}
+
+/* API: get capability */
+static pj_status_t null_stream_get_cap(pjmedia_vid_dev_stream *s,
+				       pjmedia_vid_dev_cap cap,
+				       void *pval)
+{
+    struct null_stream *strm = (struct null_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+    return PJMEDIA_EVID_INVCAP;
+}
+
+/* API: set capability */
+static pj_status_t null_stream_set_cap(pjmedia_vid_dev_stream *s,
+				       pjmedia_vid_dev_cap cap,
+				       const void *pval)
+{
+    struct null_stream *strm = (struct null_stream*)s;
+
+    PJ_UNUSED_ARG(strm);
+    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
+    return PJMEDIA_EVID_INVCAP;
+}
+
+
+/* API: Get frame from stream */
+static pj_status_t null_stream_get_frame(pjmedia_vid_dev_stream *strm,
+                                         pjmedia_frame *frame)
+{
+    struct null_stream *stream = (struct null_stream*)strm;
+    unsigned i;
+    pj_uint8_t *ptr = frame->buf;
+
+    frame->type = PJMEDIA_FRAME_TYPE_VIDEO;
+    frame->bit_info = 0;
+    frame->timestamp = stream->ts;
+    stream->ts.u64 += stream->ts_inc;
+
+    /* paint subsequent lines */
+    for (i=0; i<stream->vfi->plane_cnt; ++i) {
+        pj_uint8_t *plane_end;
+        plane_end = ptr + stream->vafp.plane_bytes[i];
+        while (ptr < plane_end) {
+            pj_memcpy(ptr, stream->first_line[i], stream->vafp.strides[i]);
+            ptr += stream->vafp.strides[i];
+        }
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* API: Start stream. */
+static pj_status_t null_stream_start(pjmedia_vid_dev_stream *strm)
+{
+    struct null_stream *stream = (struct null_stream*)strm;
+
+    PJ_UNUSED_ARG(stream);
+
+    PJ_LOG(4, (THIS_FILE, "Starting null video stream"));
+
+    return PJ_SUCCESS;
+}
+
+/* API: Stop stream. */
+static pj_status_t null_stream_stop(pjmedia_vid_dev_stream *strm)
+{
+    struct null_stream *stream = (struct null_stream*)strm;
+
+    PJ_UNUSED_ARG(stream);
+
+    PJ_LOG(4, (THIS_FILE, "Stopping null video stream"));
+
+    return PJ_SUCCESS;
+}
+
+
+/* API: Destroy stream. */
+static pj_status_t null_stream_destroy(pjmedia_vid_dev_stream *strm)
+{
+    struct null_stream *stream = (struct null_stream*)strm;
+
+    PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
+
+    null_stream_stop(strm);
+
+    pj_pool_release(stream->pool);
+
+    return PJ_SUCCESS;
+}
+
+#endif	/* PJMEDIA_VIDEO_DEV_HAS_NULL */
diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/videodev.c pjsip/pjmedia/src/pjmedia-videodev/videodev.c
--- pjproject-2.10/pjmedia/src/pjmedia-videodev/videodev.c	2020-02-14 10:48:27.000000000 +0100
+++ pjsip/pjmedia/src/pjmedia-videodev/videodev.c	2021-02-06 23:01:33.883264555 +0100
@@ -51,6 +51,10 @@
 pjmedia_vid_dev_factory* pjmedia_qt_factory(pj_pool_factory *pf);
 #endif
 
+#if PJMEDIA_VIDEO_DEV_HAS_AVF
+pjmedia_vid_dev_factory* pjmedia_avf_factory(pj_pool_factory *pf);
+#endif
+
 #if PJMEDIA_VIDEO_DEV_HAS_DARWIN
 pjmedia_vid_dev_factory* pjmedia_darwin_factory(pj_pool_factory *pf);
 #endif
@@ -59,6 +63,14 @@
 pjmedia_vid_dev_factory* pjmedia_opengl_factory(pj_pool_factory *pf);
 #endif
 
+#if PJMEDIA_VIDEO_DEV_HAS_FB
+pjmedia_vid_dev_factory* pjmedia_fb_factory(pj_pool_factory *pf);
+#endif
+
+#if PJMEDIA_VIDEO_DEV_HAS_NULL
+pjmedia_vid_dev_factory* pjmedia_null_factory(pj_pool_factory *pf);
+#endif
+
 #if PJMEDIA_VIDEO_DEV_HAS_ANDROID
 pjmedia_vid_dev_factory* pjmedia_and_factory(pj_pool_factory *pf);
 #endif
@@ -98,6 +110,9 @@
 #if PJMEDIA_VIDEO_DEV_HAS_QT
     vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_qt_factory;
 #endif
+#if PJMEDIA_VIDEO_DEV_HAS_AVF
+    vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_avf_factory;
+#endif
 #if PJMEDIA_VIDEO_DEV_HAS_OPENGL
     vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_opengl_factory;
 #endif
@@ -122,6 +137,12 @@
      */
     vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_cbar_factory;
 #endif
+#if PJMEDIA_VIDEO_DEV_HAS_FB
+    vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_fb_factory;
+#endif
+#if PJMEDIA_VIDEO_DEV_HAS_NULL
+    vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_null_factory;
+#endif
 
     /* Initialize each factory and build the device ID list */
     for (i=0; i<vid_subsys->drv_cnt; ++i) {
--- pjsip/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c	2021-03-19 17:02:04.749861806 +0100
+++ pjsip/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c	2021-03-19 17:03:03.618376942 +0100
@@ -58,9 +58,9 @@
 
 #define MAX_DEV_CNT     8
 
-#ifndef PJMEDIA_USE_OLD_FFMPEG
+//#ifndef PJMEDIA_USE_OLD_FFMPEG
 #  define av_close_input_stream(ctx) avformat_close_input(&ctx)
-#endif
+//#endif
 
 
 typedef struct ffmpeg_dev_info
