From cc8fb024626b54948230706175890c933bd06896 Mon Sep 17 00:00:00 2001
From: agsantos <aline.gondimsantos@savoirfairelinux.com>
Date: Tue, 16 Nov 2021 09:23:04 -0500
Subject: [PATCH] x11: window sharing

+ If in screen sharing, the hardware acceleration encoding is disabled

GitLab: https://git.jami.net/savoirfairelinux/jami-project/-/issues/1294

Change-Id: I24982b4454d724bb86059a50a3c2d01fe6fab147
---
 contrib/src/ffmpeg/rules.mak                  |   3 +
 .../src/ffmpeg/screen-sharing-x11-fix.patch   | 302 ++++++++++++++++++
 src/media/media_decoder.cpp                   |  65 +++-
 src/media/media_decoder.h                     |  16 +-
 src/media/media_device.h                      |   2 +
 src/media/media_encoder.cpp                   |  72 +++--
 src/media/media_encoder.h                     |   1 +
 src/media/video/sinkclient.cpp                |   5 +-
 src/media/video/video_device_monitor.cpp      |   4 +-
 src/media/video/video_input.cpp               |  68 ++--
 src/media/video/video_input.h                 |   6 +-
 src/media/video/video_rtp_session.cpp         |  10 +-
 src/media/video/video_sender.cpp              |  21 +-
 src/media/video/video_sender.h                |   3 +-
 14 files changed, 511 insertions(+), 67 deletions(-)
 create mode 100644 contrib/src/ffmpeg/screen-sharing-x11-fix.patch

diff --git a/contrib/src/ffmpeg/rules.mak b/contrib/src/ffmpeg/rules.mak
index df6b3711e5..2a9a58391a 100644
--- a/contrib/src/ffmpeg/rules.mak
+++ b/contrib/src/ffmpeg/rules.mak
@@ -3,9 +3,11 @@ FFMPEG_URL := https://git.ffmpeg.org/gitweb/ffmpeg.git/snapshot/$(FFMPEG_HASH).t
 
 PKGS+=ffmpeg
 
+ifndef HAVE_LINUX
 ifeq ($(call need_pkg,"libavutil >= 55.75.100 libavcodec >= 57.106.101 libavformat >= 57.82.100 libavdevice >= 57.8.101 libavfilter >= 6.105.100 libswscale >= 4.7.103 libswresample >= 2.9.100"),)
 PKGS_FOUND += ffmpeg
 endif
+endif
 
 DEPS_ffmpeg = iconv zlib vpx opus speex x264
 
@@ -348,6 +350,7 @@ ffmpeg: ffmpeg-$(FFMPEG_HASH).tar.gz
 	$(APPLY) $(SRC)/ffmpeg/rtp_ext_abs_send_time.patch
 	$(APPLY) $(SRC)/ffmpeg/libopusdec-enable-FEC.patch
 	$(APPLY) $(SRC)/ffmpeg/libopusenc-enable-FEC.patch
+	$(APPLY) $(SRC)/ffmpeg/screen-sharing-x11-fix.patch
 ifdef HAVE_IOS
 	$(APPLY) $(SRC)/ffmpeg/ios-disable-b-frames.patch
 endif
diff --git a/contrib/src/ffmpeg/screen-sharing-x11-fix.patch b/contrib/src/ffmpeg/screen-sharing-x11-fix.patch
new file mode 100644
index 0000000000..9ae7dafebe
--- /dev/null
+++ b/contrib/src/ffmpeg/screen-sharing-x11-fix.patch
@@ -0,0 +1,302 @@
+From f498c487a24bd39f1adfaa51b415e93456d36612 Mon Sep 17 00:00:00 2001
+From: agsantos <aline.gondimsantos@savoirfairelinux.com>
+Date: Wed, 17 Nov 2021 12:37:32 -0500
+Subject: [PATCH] Screen sharing x11 fixes
+
++ We can now have a single stream in the x11grab, which can be updated to follow window resizing
++ Due to stream reinit, shm may cause memory issues and was removed
++ Adds one option (is_area) that defines if we are grabing a region of the display/window or the hole screen/window.
+
+note: This is a custom patch for later rebase
+---
+ libavdevice/xcbgrab.c | 186 ++++++++++--------------------------------
+ 1 file changed, 45 insertions(+), 141 deletions(-)
+
+diff --git a/libavdevice/xcbgrab.c b/libavdevice/xcbgrab.c
+index 8e3292e577..406716682f 100644
+--- a/libavdevice/xcbgrab.c
++++ b/libavdevice/xcbgrab.c
+@@ -29,11 +29,6 @@
+ #include <xcb/xfixes.h>
+ #endif
+ 
+-#if CONFIG_LIBXCB_SHM
+-#include <sys/shm.h>
+-#include <xcb/shm.h>
+-#endif
+-
+ #if CONFIG_LIBXCB_SHAPE
+ #include <xcb/shape.h>
+ #endif
+@@ -53,9 +48,6 @@ typedef struct XCBGrabContext {
+     xcb_connection_t *conn;
+     xcb_screen_t *screen;
+     xcb_window_t window;
+-#if CONFIG_LIBXCB_SHM
+-    AVBufferPool *shm_pool;
+-#endif
+     int64_t time_frame;
+     AVRational time_base;
+     int64_t frame_duration;
+@@ -72,10 +64,9 @@ typedef struct XCBGrabContext {
+     int region_border;
+     int centered;
+     int select_region;
++    int is_area;
+ 
+     const char *framerate;
+-
+-    int has_shm;
+ } XCBGrabContext;
+ 
+ #define FOLLOW_CENTER -1
+@@ -97,6 +88,7 @@ static const AVOption options[] = {
+     { "show_region", "Show the grabbing region.", OFFSET(show_region), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, D },
+     { "region_border", "Set the region border thickness.", OFFSET(region_border), AV_OPT_TYPE_INT, { .i64 = 3 }, 1, 128, D },
+     { "select_region", "Select the grabbing region graphically using the pointer.", OFFSET(select_region), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
++    { "is_area", "Define if we are grabing a region of the display/window.", OFFSET(is_area), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, D },
+     { NULL },
+ };
+ 
+@@ -216,99 +208,6 @@ static int64_t wait_frame(AVFormatContext *s, AVPacket *pkt)
+     return curtime;
+ }
+ 
+-#if CONFIG_LIBXCB_SHM
+-static int check_shm(xcb_connection_t *conn)
+-{
+-    xcb_shm_query_version_cookie_t cookie = xcb_shm_query_version(conn);
+-    xcb_shm_query_version_reply_t *reply;
+-
+-    reply = xcb_shm_query_version_reply(conn, cookie, NULL);
+-    if (reply) {
+-        free(reply);
+-        return 1;
+-    }
+-
+-    return 0;
+-}
+-
+-static void free_shm_buffer(void *opaque, uint8_t *data)
+-{
+-    shmdt(data);
+-}
+-
+-static AVBufferRef *allocate_shm_buffer(void *opaque, buffer_size_t size)
+-{
+-    xcb_connection_t *conn = opaque;
+-    xcb_shm_seg_t segment;
+-    AVBufferRef *ref;
+-    uint8_t *data;
+-    int id;
+-
+-    id = shmget(IPC_PRIVATE, size, IPC_CREAT | 0777);
+-    if (id == -1)
+-        return NULL;
+-
+-    segment = xcb_generate_id(conn);
+-    xcb_shm_attach(conn, segment, id, 0);
+-    data = shmat(id, NULL, 0);
+-    shmctl(id, IPC_RMID, 0);
+-    if ((intptr_t)data == -1 || !data)
+-        return NULL;
+-
+-    ref = av_buffer_create(data, size, free_shm_buffer, (void *)(ptrdiff_t)segment, 0);
+-    if (!ref)
+-        shmdt(data);
+-
+-    return ref;
+-}
+-
+-static int xcbgrab_frame_shm(AVFormatContext *s, AVPacket *pkt)
+-{
+-    XCBGrabContext *c = s->priv_data;
+-    xcb_shm_get_image_cookie_t iq;
+-    xcb_shm_get_image_reply_t *img;
+-    xcb_drawable_t drawable = c->window_id;
+-    xcb_generic_error_t *e = NULL;
+-    AVBufferRef *buf;
+-    xcb_shm_seg_t segment;
+-
+-    buf = av_buffer_pool_get(c->shm_pool);
+-    if (!buf) {
+-        av_log(s, AV_LOG_ERROR, "Could not get shared memory buffer.\n");
+-        return AVERROR(ENOMEM);
+-    }
+-    segment = (xcb_shm_seg_t)av_buffer_pool_buffer_get_opaque(buf);
+-
+-    iq = xcb_shm_get_image(c->conn, drawable,
+-                           c->x, c->y, c->width, c->height, ~0,
+-                           XCB_IMAGE_FORMAT_Z_PIXMAP, segment, 0);
+-    img = xcb_shm_get_image_reply(c->conn, iq, &e);
+-
+-    xcb_flush(c->conn);
+-
+-    if (e) {
+-        av_log(s, AV_LOG_ERROR,
+-               "Cannot get the image data "
+-               "event_error: response_type:%u error_code:%u "
+-               "sequence:%u resource_id:%u minor_code:%u major_code:%u.\n",
+-               e->response_type, e->error_code,
+-               e->sequence, e->resource_id, e->minor_code, e->major_code);
+-
+-        free(e);
+-        av_buffer_unref(&buf);
+-        return AVERROR(EACCES);
+-    }
+-
+-    free(img);
+-
+-    pkt->buf = buf;
+-    pkt->data = buf->data;
+-    pkt->size = c->frame_size;
+-
+-    return 0;
+-}
+-#endif /* CONFIG_LIBXCB_SHM */
+-
+ #if CONFIG_LIBXCB_XFIXES
+ static int check_xfixes(xcb_connection_t *conn)
+ {
+@@ -462,14 +361,7 @@ static int xcbgrab_read_packet(AVFormatContext *s, AVPacket *pkt)
+     if (c->show_region)
+         xcbgrab_update_region(s, win_x, win_y);
+ 
+-#if CONFIG_LIBXCB_SHM
+-    if (c->has_shm && xcbgrab_frame_shm(s, pkt) < 0) {
+-        av_log(s, AV_LOG_WARNING, "Continuing without shared memory.\n");
+-        c->has_shm = 0;
+-    }
+-#endif
+-    if (!c->has_shm)
+-        ret = xcbgrab_frame(s, pkt);
++    ret = xcbgrab_frame(s, pkt);
+     pkt->dts = pkt->pts = pts;
+     pkt->duration = c->frame_duration;
+ 
+@@ -488,11 +380,8 @@ static av_cold int xcbgrab_read_close(AVFormatContext *s)
+ {
+     XCBGrabContext *ctx = s->priv_data;
+ 
+-#if CONFIG_LIBXCB_SHM
+-    av_buffer_pool_uninit(&ctx->shm_pool);
+-#endif
+-
+     xcb_disconnect(ctx->conn);
++    ctx->conn = NULL;
+ 
+     return 0;
+ }
+@@ -572,7 +461,15 @@ static int pixfmt_from_pixmap_format(AVFormatContext *s, int depth,
+ static int create_stream(AVFormatContext *s)
+ {
+     XCBGrabContext *c = s->priv_data;
+-    AVStream *st      = avformat_new_stream(s, NULL);
++
++    // If we try to open another stream to x11grab, there is no reason
++    // to keep more than one stream in the context.
++    AVStream *st;
++    if (!s->nb_streams) {
++        st = avformat_new_stream(s, NULL);
++    } else {
++        st = s->streams[0];
++    }
+     xcb_get_geometry_cookie_t gc;
+     xcb_get_geometry_reply_t *geo;
+     int64_t frame_size_bits;
+@@ -594,11 +491,26 @@ static int create_stream(AVFormatContext *s)
+         return AVERROR_EXTERNAL;
+     }
+ 
++    // av_log(s, AV_LOG_ERROR, "Capture is_area %d\n", c->is_area);
++    // Width and Height are not 0 only when we set a window area to share
++    // This if may be valid only in  the first call to create_stream
+     if (!c->width || !c->height) {
++        // av_log(s, AV_LOG_ERROR, "Capture area!\n");
++        c->is_area = 0;
++        c->width = geo->width;
++        c->height = geo->height;
++    }
++    // If not a predefined area, then we should follow geometry changes
++    // This can be valid only on the second call onwards
++    if (!c->is_area && (c->width != geo->width || c->height != geo->height)) {
+         c->width = geo->width;
+         c->height = geo->height;
+     }
+ 
++    // av_log(s, AV_LOG_ERROR, "Capture area %dx%d at position %d.%d\n",
++    //            c->width, c->height,
++    //            c->x, c->y);
++
+     if (c->x + c->width > geo->width ||
+         c->y + c->height > geo->height) {
+         av_log(s, AV_LOG_ERROR,
+@@ -628,13 +540,6 @@ static int create_stream(AVFormatContext *s)
+     }
+     c->frame_size = frame_size_bits / 8;
+ 
+-#if CONFIG_LIBXCB_SHM
+-    c->shm_pool = av_buffer_pool_init2(c->frame_size + AV_INPUT_BUFFER_PADDING_SIZE,
+-                                           c->conn, allocate_shm_buffer, NULL);
+-    if (!c->shm_pool)
+-        return AVERROR(ENOMEM);
+-#endif
+-
+     st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
+     st->codecpar->codec_id   = AV_CODEC_ID_RAWVIDEO;
+     st->codecpar->width      = c->width;
+@@ -829,23 +734,26 @@ static av_cold int xcbgrab_read_header(AVFormatContext *s)
+         sscanf(s->url, "+%d,%d", &c->x, &c->y);
+     }
+ 
+-    c->conn = xcb_connect(display_name[0] ? display_name : NULL, &screen_num);
+-    av_freep(&display_name);
++    if (!c->conn || !c->screen) {
++        xcbgrab_read_close(s);
++        c->conn = xcb_connect(display_name[0] ? display_name : NULL, &screen_num);
++        av_freep(&display_name);
+ 
+-    if ((ret = xcb_connection_has_error(c->conn))) {
+-        av_log(s, AV_LOG_ERROR, "Cannot open display %s, error %d.\n",
+-               s->url[0] ? s->url : "default", ret);
+-        return AVERROR(EIO);
+-    }
++        if ((ret = xcb_connection_has_error(c->conn))) {
++            av_log(s, AV_LOG_ERROR, "Cannot open display %s, error %d.\n",
++                s->url[0] ? s->url : "default", ret);
++            return AVERROR(EIO);
++        }
+ 
+-    setup = xcb_get_setup(c->conn);
++        setup = xcb_get_setup(c->conn);
+ 
+-    c->screen = get_screen(setup, screen_num);
+-    if (!c->screen) {
+-        av_log(s, AV_LOG_ERROR, "The screen %d does not exist.\n",
+-               screen_num);
+-        xcbgrab_read_close(s);
+-        return AVERROR(EIO);
++        c->screen = get_screen(setup, screen_num);
++        if (!c->screen) {
++            av_log(s, AV_LOG_ERROR, "The screen %d does not exist.\n",
++                screen_num);
++            xcbgrab_read_close(s);
++            return AVERROR(EIO);
++        }
+     }
+ 
+     if (c->window_id == XCB_NONE)
+@@ -876,10 +784,6 @@ static av_cold int xcbgrab_read_header(AVFormatContext *s)
+         return ret;
+     }
+ 
+-#if CONFIG_LIBXCB_SHM
+-    c->has_shm = check_shm(c->conn);
+-#endif
+-
+ #if CONFIG_LIBXCB_XFIXES
+     if (c->draw_mouse) {
+         if (!(c->draw_mouse = check_xfixes(c->conn))) {
+-- 
+2.30.2
+
diff --git a/src/media/media_decoder.cpp b/src/media/media_decoder.cpp
index f7c1c44730..5aed8616dc 100644
--- a/src/media/media_decoder.cpp
+++ b/src/media/media_decoder.cpp
@@ -117,6 +117,10 @@ MediaDemuxer::openInput(const DeviceParams& params)
     if (!params.pixel_format.empty()) {
         av_dict_set(&options_, "pixel_format", params.pixel_format.c_str(), 0);
     }
+    if (!params.window_id.empty()) {
+        av_dict_set(&options_, "window_id", params.window_id.c_str(), 0);
+    }
+    av_dict_set(&options_, "is_area", std::to_string(params.is_area).c_str(), 0);
 
 #if defined(__APPLE__) && TARGET_OS_MAC
     std::string input = params.name;
@@ -142,7 +146,12 @@ MediaDemuxer::openInput(const DeviceParams& params)
     if (ret) {
         JAMI_ERR("avformat_open_input failed: %s", libav_utils::getError(ret).c_str());
     } else {
-        JAMI_DBG("Using format %s", params.format.c_str());
+        baseWidth_ = inputCtx_->streams[0]->codecpar->width;
+        baseHeight_ = inputCtx_->streams[0]->codecpar->height;
+        JAMI_DBG("Using format %s and resolution %dx%d",
+                 params.format.c_str(),
+                 baseWidth_,
+                 baseHeight_);
     }
 
     return ret;
@@ -321,18 +330,35 @@ MediaDemuxer::decode()
                                                                                     av_packet_free(
                                                                                         &p);
                                                                             });
+    if (inputParams_.format == "x11grab") {
+        auto ret = inputCtx_->iformat->read_header(inputCtx_);
+        if (ret == AVERROR_EXTERNAL) {
+            JAMI_ERR("Couldn't read frame: %s\n", libav_utils::getError(ret).c_str());
+            return Status::ReadError;
+        }
+        auto codecpar = inputCtx_->streams[0]->codecpar;
+        if (baseHeight_ != codecpar->height || baseWidth_ != codecpar->width) {
+            baseHeight_ = codecpar->height;
+            baseWidth_ = codecpar->width;
+            inputParams_.height = ((baseHeight_ >> 3) << 3);
+            inputParams_.width = ((baseWidth_ >> 3) << 3);
+            return Status::RestartRequired;
+        }
+    }
 
     int ret = av_read_frame(inputCtx_, packet.get());
     if (ret == AVERROR(EAGAIN)) {
         /*no data available. Calculate time until next frame.
-         We do not use the emulated frame mechanism from the decoder because it will affect all platforms.
-         With the current implementation, the demuxer will be waiting just in case when av_read_frame
-         returns EAGAIN. For some platforms, av_read_frame is blocking and it will never happen.
+         We do not use the emulated frame mechanism from the decoder because it will affect all
+         platforms. With the current implementation, the demuxer will be waiting just in case when
+         av_read_frame returns EAGAIN. For some platforms, av_read_frame is blocking and it will
+         never happen.
          */
         if (inputParams_.framerate.numerator() == 0)
             return Status::Success;
-        rational<double> frameTime = 1e6/inputParams_.framerate;
-        int64_t timeToSleep = lastReadPacketTime_ - av_gettime_relative() + frameTime.real<int64_t>();
+        rational<double> frameTime = 1e6 / inputParams_.framerate;
+        int64_t timeToSleep = lastReadPacketTime_ - av_gettime_relative()
+                              + frameTime.real<int64_t>();
         if (timeToSleep <= 0) {
             return Status::Success;
         }
@@ -340,6 +366,8 @@ MediaDemuxer::decode()
         return Status::Success;
     } else if (ret == AVERROR_EOF) {
         return Status::EndOfFile;
+    } else if (ret == AVERROR(EACCES)) {
+        return Status::RestartRequired;
     } else if (ret < 0) {
         JAMI_ERR("Couldn't read frame: %s\n", libav_utils::getError(ret).c_str());
         return Status::ReadError;
@@ -570,9 +598,11 @@ MediaDecoder::decode(AVPacket& packet)
             avcodec_flush_buffers(decoderCtx_);
             setupStream();
             return DecodeStatus::FallBack;
-        } else
+        }
 #endif
-            return ret == AVERROR_EOF ? DecodeStatus::Success : DecodeStatus::DecodeError;
+        avcodec_flush_buffers(decoderCtx_);
+        setupStream();
+        return ret == AVERROR_EOF ? DecodeStatus::Success : DecodeStatus::DecodeError;
     }
 
     auto f = (inputDecoder_->type == AVMEDIA_TYPE_VIDEO)
@@ -583,10 +613,10 @@ MediaDecoder::decode(AVPacket& packet)
     if (resolutionChangedCallback_) {
         if (decoderCtx_->width != width_ or decoderCtx_->height != height_) {
             JAMI_DBG("Resolution changed from %dx%d to %dx%d",
-                width_,
-                height_,
-                decoderCtx_->width,
-                decoderCtx_->height);
+                     width_,
+                     height_,
+                     decoderCtx_->width,
+                     decoderCtx_->height);
             width_ = decoderCtx_->width;
             height_ = decoderCtx_->height;
             resolutionChangedCallback_(width_, height_);
@@ -613,7 +643,8 @@ MediaDecoder::decode(AVPacket& packet)
         lastTimestamp_ = frame->pts;
         if (emulateRate_ and packetTimestamp != AV_NOPTS_VALUE) {
             auto startTime = avStream_->start_time == AV_NOPTS_VALUE ? 0 : avStream_->start_time;
-            rational<double> frame_time = rational<double>(getTimeBase()) * (packetTimestamp - startTime);
+            rational<double> frame_time = rational<double>(getTimeBase())
+                                          * (packetTimestamp - startTime);
             auto target_relative = static_cast<std::int64_t>(frame_time.real() * 1e6);
             auto target_absolute = startTime_ + target_relative;
             if (target_relative < seekTime_) {
@@ -645,7 +676,13 @@ MediaDecoder::setSeekTime(int64_t time)
 MediaDemuxer::Status
 MediaDecoder::decode()
 {
-    return demuxer_->decode();
+    auto ret = demuxer_->decode();
+    if (ret == MediaDemuxer::Status::RestartRequired) {
+        avcodec_flush_buffers(decoderCtx_);
+        setupStream();
+        ret = MediaDemuxer::Status::EndOfFile;
+    }
+    return ret;
 }
 
 #ifdef ENABLE_VIDEO
diff --git a/src/media/media_decoder.h b/src/media/media_decoder.h
index a296d5397b..4d47882a09 100644
--- a/src/media/media_decoder.h
+++ b/src/media/media_decoder.h
@@ -85,7 +85,14 @@ public:
     MediaDemuxer();
     ~MediaDemuxer();
 
-    enum class Status { Success, EndOfFile, ReadBufferOverflow, ReadError, FallBack };
+    enum class Status {
+        Success,
+        EndOfFile,
+        ReadBufferOverflow,
+        ReadError,
+        FallBack,
+        RestartRequired
+    };
 
     enum class CurrentState { Demuxing, Finished };
     using StreamCallback = std::function<DecodeStatus(AVPacket&)>;
@@ -145,6 +152,8 @@ private:
     void pushFrameFrom(std::queue<std::unique_ptr<AVPacket, std::function<void(AVPacket*)>>>& buffer,
                        bool isAudio,
                        std::mutex& mutex);
+    int baseWidth_ {};
+    int baseHeight_ {};
 };
 
 class MediaDecoder
@@ -190,7 +199,10 @@ public:
 
     MediaStream getStream(std::string name = "") const;
 
-    void setResolutionChangedCallback(std::function<void(int, int)> cb) { resolutionChangedCallback_ = std::move(cb); }
+    void setResolutionChangedCallback(std::function<void(int, int)> cb)
+    {
+        resolutionChangedCallback_ = std::move(cb);
+    }
 
     void setFEC(bool enable) { fecEnabled_ = enable; }
 
diff --git a/src/media/media_device.h b/src/media/media_device.h
index 3556a9e0db..1a0a6bf79b 100644
--- a/src/media/media_device.h
+++ b/src/media/media_device.h
@@ -51,6 +51,8 @@ struct DeviceParams
     int offset_x {};
     int offset_y {};
     int orientation {};
+    std::string window_id {};
+    int is_area {};
 };
 
 } // namespace jami
diff --git a/src/media/media_encoder.cpp b/src/media/media_encoder.cpp
index 805108eb68..02798a96c2 100644
--- a/src/media/media_encoder.cpp
+++ b/src/media/media_encoder.cpp
@@ -101,12 +101,13 @@ MediaEncoder::setOptions(const MediaStream& opts)
         // Make sure width and height are even (required by x264)
         // This is especially for image/gif streaming, as video files and cameras usually have even
         // resolutions
-        videoOpts_.width -= videoOpts_.width % 2;
-        videoOpts_.height -= videoOpts_.height % 2;
+        videoOpts_.width = ((videoOpts_.width >> 3) << 3);
+        videoOpts_.height = ((videoOpts_.height >> 3) << 3);
         if (!videoOpts_.frameRate)
             videoOpts_.frameRate = 30;
-        if (!videoOpts_.bitrate)
-            videoOpts_.bitrate = SystemCodecInfo::DEFAULT_VIDEO_BITRATE;;
+        if (!videoOpts_.bitrate) {
+            videoOpts_.bitrate = SystemCodecInfo::DEFAULT_VIDEO_BITRATE;
+        }
     } else {
         audioOpts_ = opts;
     }
@@ -211,8 +212,15 @@ MediaEncoder::initStream(const SystemCodecInfo& systemCodecInfo, AVBufferRef* fr
     else if (systemCodecInfo.mediaType == MEDIA_AUDIO)
         mediaType = AVMEDIA_TYPE_AUDIO;
 
+    AVStream* stream;
     // add video stream to outputformat context
-    AVStream* stream = avformat_new_stream(outputCtx_, outputCodec_);
+    if (outputCtx_->nb_streams <= 0)
+        stream = avformat_new_stream(outputCtx_, outputCodec_);
+    else {
+        stream = outputCtx_->streams[0];
+        stream->codecpar->width = videoOpts_.width;
+        stream->codecpar->height = videoOpts_.height;
+    }
     if (!stream)
         throw MediaEncoderException("Could not allocate stream");
 
@@ -363,7 +371,9 @@ MediaEncoder::startIO()
 
 #ifdef ENABLE_VIDEO
 int
-MediaEncoder::encode(const std::shared_ptr<VideoFrame>& input, bool is_keyframe, int64_t frame_number)
+MediaEncoder::encode(const std::shared_ptr<VideoFrame>& input,
+                     bool is_keyframe,
+                     int64_t frame_number)
 {
     if (!initialized_) {
         initStream(videoCodec_, input->pointer()->hw_frames_ctx);
@@ -390,7 +400,7 @@ MediaEncoder::encode(const std::shared_ptr<VideoFrame>& input, bool is_keyframe,
     avframe->pts = frame_number;
     if (enc->framerate.num != enc->time_base.den || enc->framerate.den != enc->time_base.num)
         avframe->pts /= (rational<int64_t>(enc->framerate) * rational<int64_t>(enc->time_base))
-                          .real<int64_t>();
+                            .real<int64_t>();
 
     if (is_keyframe) {
         avframe->pict_type = AV_PICTURE_TYPE_I;
@@ -858,8 +868,7 @@ MediaEncoder::initH264(AVCodecContext* encoderCtx, uint64_t br)
     uint64_t maxBitrate = 1000 * br;
     // 200 Kbit/s    -> CRF40
     // 6 Mbit/s      -> CRF23
-    uint8_t crf = (uint8_t) std::round(
-        LOGREG_PARAM_A + LOGREG_PARAM_B*log(maxBitrate));
+    uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A + LOGREG_PARAM_B * log(maxBitrate));
     // bufsize parameter impact the variation of the bitrate, reduce to half the maxrate to limit
     // peak and congestion
     // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate
@@ -895,8 +904,8 @@ MediaEncoder::initH265(AVCodecContext* encoderCtx, uint64_t br)
         // CRF) https://slhck.info/video/2017/02/24/crf-guide.html
         // 200 Kbit/s    -> CRF35
         // 6 Mbit/s      -> CRF18
-        uint8_t crf = (uint8_t) std::round(
-            LOGREG_PARAM_A_HEVC + LOGREG_PARAM_B_HEVC*log(maxBitrate));
+        uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A_HEVC
+                                           + LOGREG_PARAM_B_HEVC * log(maxBitrate));
         uint64_t bufSize = maxBitrate / 2;
         av_opt_set_int(encoderCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN);
         av_opt_set_int(encoderCtx, "maxrate", maxBitrate, AV_OPT_SEARCH_CHILDREN);
@@ -938,8 +947,7 @@ MediaEncoder::initVP8(AVCodecContext* encoderCtx, uint64_t br)
         uint64_t maxBitrate = 1000 * br;
         // 200 Kbit/s    -> CRF40
         // 6 Mbit/s      -> CRF23
-        uint8_t crf = (uint8_t) std::round(
-            LOGREG_PARAM_A + LOGREG_PARAM_B*log(maxBitrate));
+        uint8_t crf = (uint8_t) std::round(LOGREG_PARAM_A + LOGREG_PARAM_B * log(maxBitrate));
         uint64_t bufSize = maxBitrate / 2;
 
         av_opt_set(encoderCtx, "quality", "realtime", AV_OPT_SEARCH_CHILDREN);
@@ -1194,11 +1202,12 @@ MediaEncoder::testH265Accel()
 }
 
 int
-MediaEncoder::getHWFrame(const std::shared_ptr<VideoFrame>& input, std::shared_ptr<VideoFrame>& output)
+MediaEncoder::getHWFrame(const std::shared_ptr<VideoFrame>& input,
+                         std::shared_ptr<VideoFrame>& output)
 {
     try {
 #if defined(TARGET_OS_IOS) && TARGET_OS_IOS
-// iOS
+        // iOS
         if (accel_) {
             auto pix = accel_->getSoftwareFormat();
             if (input->format() != pix) {
@@ -1211,7 +1220,7 @@ MediaEncoder::getHWFrame(const std::shared_ptr<VideoFrame>& input, std::shared_p
             output = getScaledSWFrame(*input.get());
         }
 #elif !defined(__APPLE__)
-// Other Platforms
+        // Other Platforms
         auto desc = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(input->format()));
         bool isHardware = desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL);
         if (accel_ && accel_->isLinked() && isHardware) {
@@ -1229,7 +1238,7 @@ MediaEncoder::getHWFrame(const std::shared_ptr<VideoFrame>& input, std::shared_p
             output = getScaledSWFrame(*input.get());
         }
 #else
-// macOS
+        // macOS
         output = getScaledSWFrame(*input.get());
 #endif
     } catch (const std::runtime_error& e) {
@@ -1247,8 +1256,7 @@ MediaEncoder::getUnlinkedHWFrame(const VideoFrame& input)
     std::shared_ptr<VideoFrame> framePtr = video::HardwareAccel::transferToMainMemory(input, pix);
     if (!accel_) {
         framePtr = scaler_.convertFormat(*framePtr, AV_PIX_FMT_YUV420P);
-    }
-    else {
+    } else {
         framePtr = accel_->transfer(*framePtr);
     }
     return framePtr;
@@ -1276,4 +1284,30 @@ MediaEncoder::getScaledSWFrame(const VideoFrame& input)
     return scaledFrame_;
 }
 
+void
+MediaEncoder::resetStreams(int width, int height)
+{
+    // Only called by VideoSender!
+    initialized_ = false;
+    videoOpts_.width = width;
+    videoOpts_.height = height;
+
+    try {
+        flush();
+        if (outputCtx_) {
+            for (auto encoderCtx : encoders_) {
+                if (encoderCtx) {
+#ifndef _MSC_VER
+                    avcodec_free_context(&encoderCtx);
+#else
+                    avcodec_close(encoderCtx);
+#endif
+                }
+            }
+            encoders_.clear();
+        }
+    } catch (...) {
+    }
+}
+
 } // namespace jami
diff --git a/src/media/media_encoder.h b/src/media/media_encoder.h
index 983ec23966..787f2eb9ed 100644
--- a/src/media/media_encoder.h
+++ b/src/media/media_encoder.h
@@ -77,6 +77,7 @@ public:
     void setOptions(const MediaDescription& args);
     int addStream(const SystemCodecInfo& codec);
     void setIOContext(AVIOContext* ioctx) { ioCtx_ = ioctx; }
+    void resetStreams(int width, int height);
 
     bool send(AVPacket& packet, int streamIdx = -1);
 
diff --git a/src/media/video/sinkclient.cpp b/src/media/video/sinkclient.cpp
index f9b9d1fd7a..3b6aa8fa18 100644
--- a/src/media/video/sinkclient.cpp
+++ b/src/media/video/sinkclient.cpp
@@ -350,6 +350,7 @@ SinkClient::update(Observable<std::shared_ptr<MediaFrame>>* /*obs*/,
     }
 #endif
 
+    std::lock_guard<std::mutex> lock(mtx_);
     if (avTarget_.push) {
         auto outFrame = std::make_unique<VideoFrame>();
         outFrame->copyFrom(*std::static_pointer_cast<VideoFrame>(frame_p));
@@ -363,8 +364,10 @@ SinkClient::update(Observable<std::shared_ptr<MediaFrame>>* /*obs*/,
         if (outFrame->height() != height_ || outFrame->width() != width_) {
             setFrameSize(0, 0);
             setFrameSize(outFrame->width(), outFrame->height());
+            return;
         }
         avTarget_.push(std::move(outFrame));
+        return;
     }
 
     bool doTransfer = (target_.pull != nullptr);
@@ -420,11 +423,11 @@ SinkClient::update(Observable<std::shared_ptr<MediaFrame>>* /*obs*/,
         if (frame->height() != height_ || frame->width() != width_) {
             setFrameSize(0, 0);
             setFrameSize(frame->width(), frame->height());
+            return;
         }
 #if HAVE_SHM
         shm_->renderFrame(*frame);
 #endif
-        std::lock_guard<std::mutex> lock(mtx_);
         if (target_.pull) {
             int width = frame->width();
             int height = frame->height();
diff --git a/src/media/video/video_device_monitor.cpp b/src/media/video/video_device_monitor.cpp
index 8542bd69ed..9d2e4ecbfd 100644
--- a/src/media/video/video_device_monitor.cpp
+++ b/src/media/video/video_device_monitor.cpp
@@ -102,7 +102,7 @@ VideoDeviceMonitor::getDefaultDevice() const
 {
     std::lock_guard<std::mutex> l(lock_);
     const auto it = findDeviceById(defaultDevice_);
-    if (it == std::end(devices_))
+    if (it == std::end(devices_) || it->getDeviceId() == DEVICE_DESKTOP)
         return {};
     return it->getDeviceId();
 }
@@ -112,7 +112,7 @@ VideoDeviceMonitor::getMRLForDefaultDevice() const
 {
     std::lock_guard<std::mutex> l(lock_);
     const auto it = findDeviceById(defaultDevice_);
-    if (it == std::end(devices_))
+    if (it == std::end(devices_) || it->getDeviceId() == DEVICE_DESKTOP)
         return {};
     static const std::string sep = DRing::Media::VideoProtocolPrefix::SEPARATOR;
     return DRing::Media::VideoProtocolPrefix::CAMERA + sep + it->getDeviceId();
diff --git a/src/media/video/video_input.cpp b/src/media/video/video_input.cpp
index c14e2fc516..096473c10b 100644
--- a/src/media/video/video_input.cpp
+++ b/src/media/video/video_input.cpp
@@ -204,8 +204,7 @@ void
 VideoInput::cleanup()
 {
     deleteDecoder(); // do it first to let a chance to last frame to be displayed
-    detach(sink_.get());
-    sink_->stop();
+    stopSink();
     JAMI_DBG("VideoInput closed");
 }
 
@@ -286,6 +285,10 @@ VideoInput::createDecoder()
         [](void* data) -> int { return not static_cast<VideoInput*>(data)->isCapturing(); }, this);
 
     bool ready = false, restartSink = false;
+    if (decOpts_.format == "x11grab" && !decOpts_.is_area) {
+        decOpts_.width = 0;
+        decOpts_.height = 0;
+    }
     while (!ready && !isStopped_) {
         // Retry to open the video till the input is opened
         auto ret = decoder->openInput(decOpts_);
@@ -317,10 +320,14 @@ VideoInput::createDecoder()
         return;
     }
 
-    decoder->decode(); // Populate AVCodecContext fields
+    auto ret = decoder->decode(); // Populate AVCodecContext fields
+    if (ret == MediaDemuxer::Status::ReadError) {
+        JAMI_INFO() << "Decoder error";
+        return;
+    }
 
-    decOpts_.width = decoder->getWidth();
-    decOpts_.height = decoder->getHeight();
+    decOpts_.width = ((decoder->getWidth() >> 3) << 3);
+    decOpts_.height = ((decoder->getHeight() >> 3) << 3);
     decOpts_.framerate = decoder->getFps();
     AVPixelFormat fmt = decoder->getPixelFormat();
     if (fmt != AV_PIX_FMT_NONE) {
@@ -391,29 +398,48 @@ round2pow(unsigned i, unsigned n)
 }
 
 bool
-VideoInput::initX11(std::string display)
+VideoInput::initX11(const std::string& display)
 {
+    // Patterns
+    // full screen sharing : :1+0,0 2560x1440 - SCREEN 1, POSITION 0X0, RESOLUTION 2560X1440
+    // area sharing : :1+882,211 1532x779 - SCREEN 1, POSITION 882x211, RESOLUTION 1532x779
+    // window sharing : :+1,0 0x0 window-id:0x0340021e - POSITION 0X0
     size_t space = display.find(' ');
+    std::string windowIdStr = "window-id:";
+    size_t winIdPos = display.find(windowIdStr);
 
-    clearOptions();
-    decOpts_ = jami::getVideoDeviceMonitor().getDeviceParams(DEVICE_DESKTOP);
-
+    DeviceParams p = jami::getVideoDeviceMonitor().getDeviceParams(DEVICE_DESKTOP);
+    if (winIdPos != std::string::npos) {
+        p.window_id = display.substr(winIdPos + windowIdStr.size()); // "0x0340021e";
+        p.is_area = 0;
+    }
     if (space != std::string::npos) {
-        std::istringstream iss(display.substr(space + 1));
-        char sep;
-        unsigned w, h;
-        iss >> w >> sep >> h;
-        // round to 8 pixel block
-        decOpts_.width = round2pow(w, 3);
-        decOpts_.height = round2pow(h, 3);
-        decOpts_.input = display.erase(space);
+        p.input = display.substr(1, space);
+        if (p.window_id.empty()) {
+            p.input = display.substr(0, space);
+            JAMI_INFO() << "p.window_id.empty()";
+            auto splits = jami::split_string_to_unsigned(display.substr(space + 1), 'x');
+            // round to 8 pixel block
+            p.width = round2pow(splits[0], 3);
+            p.height = round2pow(splits[1], 3);
+            p.is_area = 1;
+        }
     } else {
-        decOpts_.input = display;
-        // decOpts_.video_size = "vga";
-        decOpts_.width = default_grab_width;
-        decOpts_.height = default_grab_height;
+        p.input = display;
+        p.width = default_grab_width;
+        p.height = default_grab_height;
+        p.is_area = 1;
     }
 
+    auto dec = std::make_unique<MediaDecoder>();
+    if (dec->openInput(p) < 0 || dec->setupVideo() < 0)
+        return initCamera(jami::getVideoDeviceMonitor().getDefaultDevice());
+
+    clearOptions();
+    decOpts_ = p;
+    decOpts_.width = round2pow(dec->getStream().width, 3);
+    decOpts_.height = round2pow(dec->getStream().height, 3);
+
     return true;
 }
 
diff --git a/src/media/video/video_input.h b/src/media/video/video_input.h
index f63a997eb2..a72f433c58 100644
--- a/src/media/video/video_input.h
+++ b/src/media/video/video_input.h
@@ -99,7 +99,9 @@ public:
 #endif
 
     void setSuccessfulSetupCb(const std::function<void(MediaType, bool)>& cb)
-        { onSuccessfulSetup_ = cb; }
+    {
+        onSuccessfulSetup_ = cb;
+    }
 
 private:
     NON_COPYABLE(VideoInput);
@@ -123,7 +125,7 @@ private:
 
     // true if decOpts_ is ready to use, false if using promise/future
     bool initCamera(const std::string& device);
-    bool initX11(std::string display);
+    bool initX11(const std::string& display);
     bool initAVFoundation(const std::string& display);
     bool initFile(std::string path);
     bool initGdiGrab(const std::string& params);
diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp
index cd656a1a25..54bcd74a71 100644
--- a/src/media/video/video_rtp_session.cpp
+++ b/src/media/video/video_rtp_session.cpp
@@ -149,6 +149,7 @@ VideoRtpSession::startSender()
 
         send_.linkableHW = conference_ == nullptr;
         send_.bitrate = videoBitrateInfo_.videoBitrateCurrent;
+        bool isScreenScharing = localVideoParams_.format == "x11grab";
 
         if (socketPair_)
             initSeqVal_ = socketPair_->lastSeqValOut();
@@ -166,8 +167,13 @@ VideoRtpSession::startSender()
                                     send_.bitrate,
                                     static_cast<rational<int>>(localVideoParams_.framerate))
                       : videoMixer_->getStream("Video Sender");
-            sender_.reset(
-                new VideoSender(getRemoteRtpUri(), ms, send_, *socketPair_, initSeqVal_ + 1, mtu_));
+            sender_.reset(new VideoSender(getRemoteRtpUri(),
+                                          ms,
+                                          send_,
+                                          *socketPair_,
+                                          initSeqVal_ + 1,
+                                          mtu_,
+                                          isScreenScharing));
             if (changeOrientationCallback_)
                 sender_->setChangeOrientationCallback(changeOrientationCallback_);
             if (socketPair_)
diff --git a/src/media/video/video_sender.cpp b/src/media/video/video_sender.cpp
index 48a19d6382..fdd2c746c7 100644
--- a/src/media/video/video_sender.cpp
+++ b/src/media/video/video_sender.cpp
@@ -47,7 +47,8 @@ VideoSender::VideoSender(const std::string& dest,
                          const MediaDescription& args,
                          SocketPair& socketPair,
                          const uint16_t seqVal,
-                         uint16_t mtu)
+                         uint16_t mtu,
+                         bool isScreenScharing = false)
     : muxContext_(socketPair.createIOContext(mtu))
     , videoEncoder_(new MediaEncoder)
 {
@@ -58,10 +59,12 @@ VideoSender::VideoSender(const std::string& dest,
     videoEncoder_->addStream(args.codec->systemCodecInfo);
     videoEncoder_->setInitSeqVal(seqVal);
     videoEncoder_->setIOContext(muxContext_->getContext());
-
+#ifdef RING_ACCEL
+    if (isScreenScharing)
+        videoEncoder_->enableAccel(false);
+#endif
     // Send local video codec in SmartInfo
     Smartools::getInstance().setLocalVideoCodec(videoEncoder_->getVideoCodec());
-
     // Send the resolution in smartInfo
     Smartools::getInstance().setResolution("local", opts.width, opts.height);
 }
@@ -74,6 +77,18 @@ VideoSender::~VideoSender()
 void
 VideoSender::encodeAndSendVideo(const std::shared_ptr<VideoFrame>& input_frame)
 {
+    auto width = ((input_frame->width() >> 3) << 3);
+    auto height = ((input_frame->height() >> 3) << 3);
+    if (videoEncoder_->getWidth() != width || videoEncoder_->getHeight() != height) {
+        videoEncoder_->resetStreams(width, height);
+        forceKeyFrame();
+        // Send local video codec in SmartInfo
+        Smartools::getInstance().setLocalVideoCodec(videoEncoder_->getVideoCodec());
+        // Send the resolution in smartInfo
+        Smartools::getInstance().setResolution("local", width, height);
+        return;
+    }
+
     int angle = input_frame->getOrientation();
     if (rotation_ != angle) {
         rotation_ = angle;
diff --git a/src/media/video/video_sender.h b/src/media/video/video_sender.h
index 005027576c..0d2ca3068c 100644
--- a/src/media/video/video_sender.h
+++ b/src/media/video/video_sender.h
@@ -48,7 +48,8 @@ public:
                 const MediaDescription& args,
                 SocketPair& socketPair,
                 const uint16_t seqVal,
-                uint16_t mtu);
+                uint16_t mtu,
+                bool isScreenScharing);
 
     ~VideoSender();
 
-- 
GitLab