From ef9c62722c58239c3aa3bcc99eac6ddf4f3cc8a6 Mon Sep 17 00:00:00 2001
From: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
Date: Fri, 13 Mar 2015 11:45:37 -0400
Subject: [PATCH] video: implement frame data direct access and make shm
 optional

This patchset permits OSX client access.
This one doesn't use shared memory and requiers a direct access
to video frame.
This is done by a client side callback.

Refs #67977

Change-Id: Ib6780efe4beeab027bb868d3a124d2cec59de915
Signed-off-by: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
---
 daemon/configure.ac                           |   3 +
 daemon/src/client/videomanager.cpp            |  29 ++
 daemon/src/dring/videomanager_interface.h     |   2 +
 daemon/src/managerimpl.cpp                    |  29 ++
 daemon/src/managerimpl.h                      |  11 +
 daemon/src/media/video/sinkclient.cpp         | 289 ++++++++++--------
 daemon/src/media/video/sinkclient.h           |  56 ++--
 daemon/src/media/video/video_input.cpp        |  30 +-
 daemon/src/media/video/video_input.h          |   7 +-
 daemon/src/media/video/video_mixer.cpp        |  26 +-
 daemon/src/media/video/video_mixer.h          |   5 +-
 .../src/media/video/video_receive_thread.cpp  |  27 +-
 daemon/src/media/video/video_receive_thread.h |   6 +-
 13 files changed, 326 insertions(+), 194 deletions(-)

diff --git a/daemon/configure.ac b/daemon/configure.ac
index 3bde0a30a3..da33d01eb4 100644
--- a/daemon/configure.ac
+++ b/daemon/configure.ac
@@ -543,6 +543,9 @@ AS_IF([test "x$with_upnp" = "xyes"],
          AC_DEFINE([HAVE_LIBUPNP], 0, [Define if you have libupnp])])
       ])
 
+AC_DEFINE_UNQUOTED([HAVE_SHM], `if test -z "${HAVE_LINUX_TRUE}"; then echo 1; else echo 0; fi`,
+	[Define if you have shared memory support])
+
 # DOXYGEN
 # required dependency(ies): doxygen
 # check for doxygen, mostly stolen from http://log4cpp.sourceforge.net/
diff --git a/daemon/src/client/videomanager.cpp b/daemon/src/client/videomanager.cpp
index 822b244d03..73658c54ec 100644
--- a/daemon/src/client/videomanager.cpp
+++ b/daemon/src/client/videomanager.cpp
@@ -40,6 +40,12 @@
 #include "manager.h"
 #include "system_codec_container.h"
 #include "client/signal.h"
+#include "video/sinkclient.h"
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
 
 namespace DRing {
 
@@ -142,6 +148,29 @@ hasCameraStarted()
     return videoManager.started;
 }
 
+template <class T>
+static void
+registerSinkTarget_(const std::string& sinkId, T&& cb)
+{
+    if (auto sink = ring::Manager::instance().getSinkClient(sinkId))
+        sink->registerTarget(std::forward<T>(cb));
+    else
+        RING_WARN("No sink found for id '%s'", sinkId.c_str());
+}
+
+void
+registerSinkTarget(const std::string& sinkId,
+                   const std::function<void(unsigned char*)>& cb)
+{
+    registerSinkTarget_(sinkId, cb);
+}
+
+void
+registerSinkTarget(const std::string& sinkId,
+                   std::function<void(unsigned char*)>&& cb)
+{
+    registerSinkTarget_(sinkId, cb);
+}
 
 } // namespace DRing
 
diff --git a/daemon/src/dring/videomanager_interface.h b/daemon/src/dring/videomanager_interface.h
index e346531989..a078229729 100644
--- a/daemon/src/dring/videomanager_interface.h
+++ b/daemon/src/dring/videomanager_interface.h
@@ -74,6 +74,8 @@ void stopCamera();
 bool hasCameraStarted();
 bool switchInput(const std::string& resource);
 bool switchToCamera();
+void registerSinkTarget(const std::string& sinkId, const std::function<void(unsigned char*)>& cb);
+void registerSinkTarget(const std::string& sinkId, std::function<void(unsigned char*)>&& cb);
 
 } // namespace DRing
 
diff --git a/daemon/src/managerimpl.cpp b/daemon/src/managerimpl.cpp
index 1c11127a0a..8ef5b23162 100644
--- a/daemon/src/managerimpl.cpp
+++ b/daemon/src/managerimpl.cpp
@@ -83,6 +83,9 @@
 #include "gnutls_support.h"
 #endif
 
+#include "libav_utils.h"
+#include "video/sinkclient.h"
+
 #include <cerrno>
 #include <algorithm>
 #include <ctime>
@@ -2853,4 +2856,30 @@ ManagerImpl::newOutgoingCall(const std::string& toUrl,
     return account->newOutgoingCall(finalToUrl);
 }
 
+std::shared_ptr<video::SinkClient>
+ManagerImpl::createSinkClient(const std::string& id)
+{
+    const auto& iter = sinkMap_.find(id);
+    if (iter != std::end(sinkMap_)) {
+        if (iter->second.expired())
+            sinkMap_.erase(iter);
+        else
+            return nullptr;
+    }
+
+    auto sink = std::make_shared<video::SinkClient>(id);
+    sinkMap_.emplace(id, sink);
+    return sink;
+}
+
+std::shared_ptr<video::SinkClient>
+ManagerImpl::getSinkClient(const std::string& id)
+{
+    const auto& iter = sinkMap_.find(id);
+    if (iter != std::end(sinkMap_))
+        if (auto sink = iter->second.lock())
+            return sink;
+    return nullptr;
+}
+
 } // namespace ring
diff --git a/daemon/src/managerimpl.h b/daemon/src/managerimpl.h
index 3bc5208664..3d38b5fb9d 100644
--- a/daemon/src/managerimpl.h
+++ b/daemon/src/managerimpl.h
@@ -72,6 +72,10 @@ namespace tls {
 class GnuTlsGlobalInit;
 }
 
+namespace video {
+class SinkClient;
+}
+
 class PluginManager;
 class AudioFile;
 class DTMF;
@@ -951,6 +955,10 @@ class ManagerImpl {
 
         void addTask(const std::function<bool()>&& task);
 
+        std::shared_ptr<video::SinkClient> createSinkClient(const std::string& id="");
+
+        std::shared_ptr<video::SinkClient> getSinkClient(const std::string& id);
+
     private:
         NON_COPYABLE(ManagerImpl);
 
@@ -991,6 +999,9 @@ class ManagerImpl {
         std::unique_ptr<IceTransportFactory> ice_tf_;
 
         std::unique_ptr<tls::GnuTlsGlobalInit> gnutlGIG_;
+
+        /* Sink ID mapping */
+        std::map<std::string, std::weak_ptr<video::SinkClient>> sinkMap_;
 };
 
 } // namespace ring
diff --git a/daemon/src/media/video/sinkclient.cpp b/daemon/src/media/video/sinkclient.cpp
index ceca878300..5f8e8999d9 100644
--- a/daemon/src/media/video/sinkclient.cpp
+++ b/daemon/src/media/video/sinkclient.cpp
@@ -1,6 +1,7 @@
 /*
  *  Copyright (C) 2012-2015 Savoir-Faire Linux Inc.
  *  Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com>
+ *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
  *
  *  Portions derived from GStreamer:
  *  Copyright (C) <2009> Collabora Ltd
@@ -38,11 +39,15 @@
 #endif
 
 #include "sinkclient.h"
+
+#if HAVE_SHM
 #include "shm_header.h"
+#endif // HAVE_SHM
 
 #include "video_scaler.h"
 #include "media_buffer.h"
 #include "logger.h"
+#include "noncopyable.h"
 
 #include <sys/mman.h>
 #include <fcntl.h>
@@ -51,54 +56,85 @@
 #include <unistd.h>
 #include <cerrno>
 #include <cstring>
+#include <stdexcept>
 
 namespace ring { namespace video {
 
-SinkClient::SinkClient(const std::string &shm_name) :
-    shm_name_(shm_name)
-    , fd_(-1)
-    , shm_area_(static_cast<SHMHeader*>(MAP_FAILED))
-    , shm_area_len_(0)
-    , opened_name_()
-#ifdef DEBUG_FPS
-    , frameCount_(0u)
-    , lastFrameDebug_(std::chrono::system_clock::now())
-#endif
-{}
+#if HAVE_SHM
+// RAII class helper on sem_wait/sem_post sempahore operations
+class SemGuardLock {
+    public:
+        explicit SemGuardLock(sem_t& mutex) : m_(mutex) {
+            auto ret = ::sem_wait(&m_);
+            if (ret < 0) {
+                std::ostringstream msg;
+                msg << "SHM mutex@" << &m_
+                    << " lock failed (" << ret << ")";
+                throw std::logic_error {msg.str()};
+            }
+        }
+
+        ~SemGuardLock() {
+            ::sem_post(&m_);
+        }
+
+    private:
+        sem_t& m_;
+};
 
-SinkClient::~SinkClient()
+class ShmHolder
 {
-    stop();
-}
+    public:
+        ShmHolder(const std::string& shm_name);
+        ~ShmHolder();
 
-bool
-SinkClient::start()
+        std::string openedName() const noexcept {
+            return opened_name_;
+        }
+
+        void render_frame(VideoFrame& src);
+
+    private:
+        bool resize_area(std::size_t desired_length);
+        void alloc_area(std::size_t desired_length) noexcept;
+
+        std::string shm_name_;
+        std::string opened_name_;
+        std::size_t shm_area_len_ {0};
+        SHMHeader* shm_area_ {static_cast<SHMHeader*>(MAP_FAILED)};
+        int fd_ {-1};
+};
+
+void
+ShmHolder::alloc_area(std::size_t desired_length) noexcept
 {
-    if (fd_ != -1) {
-        RING_ERR("fd must be -1");
-        return false;
-    }
+    shm_area_ = static_cast<SHMHeader*>(::mmap(nullptr, desired_length,
+                                               PROT_READ | PROT_WRITE,
+                                               MAP_SHARED, fd_, 0));
+}
 
-    const int flags = O_RDWR | O_CREAT | O_TRUNC | O_EXCL;
-    const int perms = S_IRUSR | S_IWUSR;
+ShmHolder::ShmHolder(const std::string& shm_name) : shm_name_ {shm_name}
+{
+    static constexpr int flags = O_RDWR | O_CREAT | O_TRUNC | O_EXCL;
+    static constexpr int perms = S_IRUSR | S_IWUSR;
 
     if (not shm_name_.empty()) {
-        fd_ = shm_open(shm_name_.c_str(), flags, perms);
+        fd_ = ::shm_open(shm_name_.c_str(), flags, perms);
         if (fd_ < 0) {
-            RING_ERR("could not open shm area \"%s\"", shm_name_.c_str());
-            strErr();
-            return false;
+            std::ostringstream msg;
+            msg << "could not open shm area \""
+                << shm_name_.c_str()
+                << "\"";
+            throw std::runtime_error {msg.str()};
         }
     } else {
         for (int i = 0; fd_ < 0; ++i) {
             std::ostringstream name;
             name << PACKAGE_NAME << "_shm_" << getpid() << "_" << i;
             shm_name_ = name.str();
-            fd_ = shm_open(shm_name_.c_str(), flags, perms);
-            if (fd_ < 0 and errno != EEXIST) {
-                strErr();
-                return false;
-            }
+            fd_ = ::shm_open(shm_name_.c_str(), flags, perms);
+            if (fd_ < 0 and errno != EEXIST)
+                throw std::runtime_error {"shm_open() failed"};
         }
     }
 
@@ -107,106 +143,73 @@ SinkClient::start()
 
     shm_area_len_ = sizeof(SHMHeader);
 
-    if (ftruncate(fd_, shm_area_len_)) {
+    if (::ftruncate(fd_, shm_area_len_)) {
         RING_ERR("Could not make shm area large enough for header");
         strErr();
-        return false;
+        throw std::runtime_error {"could not make shm area large enough for header"};
     }
 
-    shm_area_ = static_cast<SHMHeader*>(mmap(NULL, shm_area_len_,
-                                             PROT_READ | PROT_WRITE,
-                                             MAP_SHARED, fd_, 0));
+    alloc_area(shm_area_len_);
 
-    if (shm_area_ == MAP_FAILED) {
-        RING_ERR("Could not map shm area, mmap failed");
-        return false;
-    }
+    if (shm_area_ == MAP_FAILED)
+        throw std::runtime_error {"could not map shm area, mmap failed"};
 
-    memset(shm_area_, 0, shm_area_len_);
-    if (sem_init(&shm_area_->notification, 1, 0) != 0) {
-        RING_ERR("sem_init: notification initialization failed");
-        return false;
-    }
-    if (sem_init(&shm_area_->mutex, 1, 1) != 0) {
-        RING_ERR("sem_init: mutex initialization failed");
-        return false;
-    }
-    return true;
+    std::memset(shm_area_, 0, shm_area_len_);
+
+    if (::sem_init(&shm_area_->notification, 1, 0) != 0)
+        throw std::runtime_error {"sem_init: notification initialization failed"};
+
+    if (::sem_init(&shm_area_->mutex, 1, 1) != 0)
+        throw std::runtime_error {"sem_init: mutex initialization failed"};
 }
 
-bool
-SinkClient::stop()
+ShmHolder::~ShmHolder()
 {
-    if (fd_ >= 0 and close(fd_) == -1)
+    if (fd_ >= 0 and ::close(fd_) == -1)
         strErr();
 
-    fd_ = -1;
+    if (not opened_name_.empty())
+        ::shm_unlink(opened_name_.c_str());
 
-    if (not opened_name_.empty()) {
-        shm_unlink(opened_name_.c_str());
-        opened_name_ = "";
+    if (shm_area_ != MAP_FAILED) {
+        ::sem_post(&shm_area_->notification);
+        if (::munmap(shm_area_, shm_area_len_) < 0)
+            strErr();
     }
-
-    if (shm_area_ != MAP_FAILED)
-        munmap(shm_area_, shm_area_len_);
-    shm_area_len_ = 0;
-    shm_area_ = static_cast<SHMHeader*>(MAP_FAILED);
-
-    return true;
 }
 
 bool
-SinkClient::resize_area(size_t desired_length)
+ShmHolder::resize_area(size_t desired_length)
 {
     if (desired_length <= shm_area_len_)
         return true;
 
-    shm_unlock();
-
-    if (munmap(shm_area_, shm_area_len_)) {
+    if (::munmap(shm_area_, shm_area_len_)) {
         RING_ERR("Could not unmap shared area");
         strErr();
         return false;
     }
 
-    if (ftruncate(fd_, desired_length)) {
+    if (::ftruncate(fd_, desired_length)) {
         RING_ERR("Could not resize shared area");
         strErr();
         return false;
     }
 
-    shm_area_ = static_cast<SHMHeader*>(mmap(NULL, desired_length,
-                                             PROT_READ | PROT_WRITE,
-                                             MAP_SHARED, fd_, 0));
-    shm_area_len_ = desired_length;
+    alloc_area(desired_length);
 
     if (shm_area_ == MAP_FAILED) {
-        shm_area_ = 0;
+        shm_area_len_ = 0;
         RING_ERR("Could not remap shared area");
         return false;
     }
 
-    shm_lock();
+    shm_area_len_ = desired_length;
     return true;
 }
 
 void
-SinkClient::render(const std::vector<unsigned char>& data)
-{
-    shm_lock();
-
-    if (!resize_area(sizeof(SHMHeader) + data.size()))
-        return;
-
-    memcpy(shm_area_->data, data.data(), data.size());
-    shm_area_->buffer_size = data.size();
-    shm_area_->buffer_gen++;
-    sem_post(&shm_area_->notification);
-    shm_unlock();
-}
-
-void
-SinkClient::render_frame(VideoFrame& src)
+ShmHolder::render_frame(VideoFrame& src)
 {
     VideoFrame dst;
     VideoScaler scaler;
@@ -214,71 +217,101 @@ SinkClient::render_frame(VideoFrame& src)
     const int width = src.width();
     const int height = src.height();
     const int format = VIDEO_PIXFMT_BGRA;
-    size_t bytes = videoFrameSize(format, width, height);
-
-    shm_lock();
+    const auto bytes = videoFrameSize(format, width, height);
 
     if (!resize_area(sizeof(SHMHeader) + bytes)) {
         RING_ERR("Could not resize area");
         return;
     }
 
+    SemGuardLock lk{shm_area_->mutex};
+
     dst.setFromMemory(shm_area_->data, format, width, height);
     scaler.scale(src, dst);
 
-#ifdef DEBUG_FPS
-    const std::chrono::time_point<std::chrono::system_clock> currentTime = \
-        std::chrono::system_clock::now();
-    const std::chrono::duration<double> seconds = currentTime - lastFrameDebug_;
-    frameCount_++;
-    if (seconds.count() > 1) {
-        RING_DBG("%s: FPS %f", shm_name_.c_str(), frameCount_ / seconds.count());
-        frameCount_ = 0;
-        lastFrameDebug_ = currentTime;
-    }
-#endif
-
     shm_area_->buffer_size = bytes;
-    shm_area_->buffer_gen++;
+    ++shm_area_->buffer_gen;
     sem_post(&shm_area_->notification);
-    shm_unlock();
 }
 
-void
-SinkClient::render_callback(VideoProvider &provider, size_t bytes)
+std::string
+SinkClient::openedName() const noexcept
 {
-    shm_lock();
+    return shm_->openedName();
+}
 
-    if (!resize_area(sizeof(SHMHeader) + bytes)) {
-        RING_ERR("Could not resize area");
-        return;
+bool
+SinkClient::start() noexcept
+{
+    if (not shm_) {
+        try {
+            shm_ = std::make_shared<ShmHolder>(id_);
+        } catch (const std::runtime_error& e) {
+            strErr();
+            RING_ERR("SHMHolder ctor failure: %s", e.what());
+        }
     }
+    return static_cast<bool>(shm_);
+}
 
-    provider.fillBuffer(static_cast<void*>(shm_area_->data));
-    shm_area_->buffer_size = bytes;
-    shm_area_->buffer_gen++;
-    sem_post(&shm_area_->notification);
-    shm_unlock();
+bool
+SinkClient::stop() noexcept
+{
+    shm_.reset();
+    return true;
 }
 
-void
-SinkClient::shm_lock()
+#else // HAVE_SHM
+
+std::string
+SinkClient::openedName() const noexcept
 {
-    sem_wait(&shm_area_->mutex);
+    return {};
 }
 
-void
-SinkClient::shm_unlock()
+bool
+SinkClient::start() noexcept
+{
+    return true;
+}
+
+bool
+SinkClient::stop() noexcept
 {
-    sem_post(&shm_area_->mutex);
+    return true;
 }
 
+#endif // !HAVE_SHM
+
+SinkClient::SinkClient(const std::string& id) : id_ {id}
+{}
+
 void
-SinkClient::update(Observable<std::shared_ptr<VideoFrame> >* /*obs*/,
-                   std::shared_ptr<VideoFrame> &frame_p)
+SinkClient::update(Observable<std::shared_ptr<VideoFrame>>* /*obs*/,
+                   std::shared_ptr<VideoFrame>& frame_p)
 {
     auto f = frame_p; // keep a local reference during rendering
-    render_frame(*f.get());
+
+#if HAVE_SHM
+    shm_->render_frame(*f.get());
+#endif
+
+    if (target_) {
+        VideoFrame dst;
+        VideoScaler scaler;
+
+        const int width = f->width();
+        const int height = f->height();
+        const int format = VIDEO_PIXFMT_BGRA;
+        const auto bytes = videoFrameSize(format, width, height);
+
+        targetData_.resize(bytes);
+        auto data = targetData_.data();
+
+        dst.setFromMemory(data, format, width, height);
+        scaler.scale(*f, dst);
+        target_(data);
+    }
 }
 
 }} // namespace ring::video
diff --git a/daemon/src/media/video/sinkclient.h b/daemon/src/media/video/sinkclient.h
index 7c6725a787..24f6d0bfab 100644
--- a/daemon/src/media/video/sinkclient.h
+++ b/daemon/src/media/video/sinkclient.h
@@ -35,51 +35,55 @@
 
 #pragma once
 
-#include "noncopyable.h"
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
 #include "video_provider.h"
 #include "video_base.h"
 
 #include <string>
 #include <vector>
-
-class SHMHeader;
+#include <memory>
 
 namespace ring { namespace video {
 
+#if HAVE_SHM
+class ShmHolder;
+#endif // HAVE_SHM
+
 class SinkClient : public VideoFramePassiveReader
 {
     public:
-        SinkClient(const std::string &shm_name = "");
-        std::string openedName() const { return opened_name_; }
-        ~SinkClient();
+        SinkClient(const std::string& id="");
 
-        bool start();
-        bool stop();
+        const std::string& getId() const noexcept {
+            return id_;
+        }
 
-        bool resize_area(size_t desired_length);
-
-        void render(const std::vector<unsigned char> &data);
-        void render_frame(VideoFrame& src);
-        void render_callback(VideoProvider &provider, size_t bytes);
+        std::string openedName() const noexcept;
 
         // as VideoFramePassiveReader
         void update(Observable<std::shared_ptr<ring::VideoFrame>>*,
-                    std::shared_ptr<ring::VideoFrame> &);
+                    std::shared_ptr<ring::VideoFrame>&);
+
+        bool start() noexcept;
+        bool stop() noexcept;
+
+        template <class T>
+        void registerTarget(T&& cb) noexcept {
+            target_ = std::forward<T>(cb);
+        }
 
     private:
-        NON_COPYABLE(SinkClient);
+        const std::string id_;
+        std::function<void(unsigned char*)> target_;
+        std::vector<unsigned char> targetData_;
 
-        void shm_lock();
-        void shm_unlock();
-        std::string shm_name_;
-        int fd_;
-        SHMHeader *shm_area_;
-        size_t shm_area_len_;
-        std::string opened_name_;
-#ifdef DEBUG_FPS
-        unsigned frameCount_;
-        std::chrono::time_point<std::chrono::system_clock> lastFrameDebug_;
-#endif
+#if HAVE_SHM
+        // using shared_ptr and not unique_ptr as ShmHolder is forwared only
+        std::shared_ptr<ShmHolder> shm_;
+#endif // HAVE_SHM
 };
 
 }} // namespace ring::video
diff --git a/daemon/src/media/video/video_input.cpp b/daemon/src/media/video/video_input.cpp
index 8cc499a0f8..a9483cbe9d 100644
--- a/daemon/src/media/video/video_input.cpp
+++ b/daemon/src/media/video/video_input.cpp
@@ -37,6 +37,7 @@
 #include "manager.h"
 #include "client/videomanager.h"
 #include "client/signal.h"
+#include "sinkclient.h"
 #include "logger.h"
 
 #include <map>
@@ -47,9 +48,9 @@
 
 namespace ring { namespace video {
 
-VideoInput::VideoInput() :
-    VideoGenerator::VideoGenerator()
-    , sink_()
+VideoInput::VideoInput()
+    : VideoGenerator::VideoGenerator()
+    , sink_ {Manager::instance().createSinkClient("local")}
     , loop_(std::bind(&VideoInput::setup, this),
             std::bind(&VideoInput::process, this),
             std::bind(&VideoInput::cleanup, this))
@@ -63,11 +64,11 @@ VideoInput::~VideoInput()
 bool VideoInput::setup()
 {
     /* Sink setup */
-    if (!sink_.start()) {
+    if (!sink_->start()) {
         RING_ERR("Cannot start shared memory sink");
         return false;
     }
-    if (not attach(&sink_))
+    if (not attach(sink_.get()))
         RING_WARN("Failed to attach sink");
 
     return true;
@@ -92,11 +93,14 @@ void VideoInput::process()
 
     if (newDecoderCreated) {
         /* Signal the client about the new sink */
-        emitSignal<DRing::VideoSignal::DecodingStarted>(sinkID_, sink_.openedName(),
-                    decoder_->getWidth(), decoder_->getHeight(), false);
+        emitSignal<DRing::VideoSignal::DecodingStarted>(sink_->getId(),
+                                                        sink_->openedName(),
+                                                        decoder_->getWidth(),
+                                                        decoder_->getHeight(),
+                                                        false);
         RING_DBG("LOCAL: shm sink <%s> started: size = %dx%d",
-              sink_.openedName().c_str(), decoder_->getWidth(),
-              decoder_->getHeight());
+                 sink_->openedName().c_str(), decoder_->getWidth(),
+                 decoder_->getHeight());
     }
 }
 
@@ -104,8 +108,8 @@ void VideoInput::cleanup()
 {
     deleteDecoder();
 
-    if (detach(&sink_))
-        sink_.stop();
+    if (detach(sink_.get()))
+        sink_->stop();
 }
 
 void VideoInput::clearOptions()
@@ -191,7 +195,9 @@ VideoInput::deleteDecoder()
     if (not decoder_)
         return;
 
-    emitSignal<DRing::VideoSignal::DecodingStopped>(sinkID_, sink_.openedName(), false);
+    emitSignal<DRing::VideoSignal::DecodingStopped>(sink_->getId(),
+                                                    sink_->openedName(),
+                                                    false);
     flushFrames();
     delete decoder_;
     decoder_ = nullptr;
diff --git a/daemon/src/media/video/video_input.h b/daemon/src/media/video/video_input.h
index 056b35b4f2..0e4610ea97 100644
--- a/daemon/src/media/video/video_input.h
+++ b/daemon/src/media/video/video_input.h
@@ -35,7 +35,6 @@
 #define __VIDEO_INPUT_H__
 
 #include "noncopyable.h"
-#include "sinkclient.h"
 #include "threadloop.h"
 #include "media/media_device.h" // DeviceParams
 
@@ -50,6 +49,8 @@ class MediaDecoder;
 
 namespace ring { namespace video {
 
+class SinkClient;
+
 class VideoInput : public VideoGenerator
 {
 public:
@@ -67,10 +68,8 @@ public:
 private:
     NON_COPYABLE(VideoInput);
 
-    std::string sinkID_     = "local";
-
     MediaDecoder *decoder_  = nullptr;
-    SinkClient sink_;
+    std::shared_ptr<SinkClient> sink_;
     std::atomic<bool> switchPending_ = {false};
 
     DeviceParams decOpts_;
diff --git a/daemon/src/media/video/video_mixer.cpp b/daemon/src/media/video/video_mixer.cpp
index ce23ccbe4b..f699550a89 100644
--- a/daemon/src/media/video/video_mixer.cpp
+++ b/daemon/src/media/video/video_mixer.cpp
@@ -37,6 +37,7 @@
 #include "client/videomanager.h"
 #include "client/signal.h"
 #include "manager.h"
+#include "sinkclient.h"
 #include "logger.h"
 
 #include <cmath>
@@ -49,8 +50,10 @@ static const double FRAME_DURATION = 1/30.;
 VideoMixer::VideoMixer(const std::string &id)
     : VideoGenerator::VideoGenerator()
     , id_(id)
-    , sink_(id)
-    , loop_([]{return true;}, std::bind(&VideoMixer::process, this), []{})
+    , sink_ {Manager::instance().createSinkClient(id)}
+    , loop_([]{return true;},
+            std::bind(&VideoMixer::process, this),
+            []{})
 {
     // Local video camera is the main participant
     videoLocal_ = getVideoCamera();
@@ -190,11 +193,14 @@ void VideoMixer::setDimensions(int width, int height)
 
 void VideoMixer::start_sink()
 {
-    if (sink_.start()) {
-        if (this->attach(&sink_)) {
-            emitSignal<DRing::VideoSignal::DecodingStarted>(id_, sink_.openedName(), width_, height_, true);
+    if (sink_->start()) {
+        if (this->attach(sink_.get())) {
+            emitSignal<DRing::VideoSignal::DecodingStarted>(id_,
+                                                            sink_->openedName(),
+                                                            width_, height_,
+                                                            true);
             RING_DBG("MX: shm sink <%s> started: size = %dx%d",
-                  sink_.openedName().c_str(), width_, height_);
+                  sink_->openedName().c_str(), width_, height_);
         }
     } else
         RING_WARN("MX: sink startup failed");
@@ -202,9 +208,11 @@ void VideoMixer::start_sink()
 
 void VideoMixer::stop_sink()
 {
-    if (this->detach(&sink_)) {
-        emitSignal<DRing::VideoSignal::DecodingStopped>(id_, sink_.openedName(), true);
-        sink_.stop();
+    if (this->detach(sink_.get())) {
+        emitSignal<DRing::VideoSignal::DecodingStopped>(id_,
+                                                        sink_->openedName(),
+                                                        true);
+        sink_->stop();
     }
 }
 
diff --git a/daemon/src/media/video/video_mixer.h b/daemon/src/media/video/video_mixer.h
index b2ee6838af..6333cb52a8 100644
--- a/daemon/src/media/video/video_mixer.h
+++ b/daemon/src/media/video/video_mixer.h
@@ -35,7 +35,6 @@
 #include "noncopyable.h"
 #include "video_base.h"
 #include "video_scaler.h"
-#include "sinkclient.h"
 #include "threadloop.h"
 #include "rw_mutex.h"
 
@@ -45,6 +44,8 @@
 
 namespace ring { namespace video {
 
+class SinkClient;
+
     struct VideoMixerSource {
         Observable<std::shared_ptr<VideoFrame>>* source = nullptr;
         std::unique_ptr<VideoFrame> update_frame;
@@ -93,7 +94,7 @@ private:
     std::list<VideoMixerSource *> sources_ = {};
     rw_mutex rwMutex_ = {};
 
-    SinkClient sink_;
+    std::shared_ptr<SinkClient> sink_;
 
     ThreadLoop loop_;
     std::chrono::time_point<std::chrono::system_clock> lastProcess_ = {};
diff --git a/daemon/src/media/video/video_receive_thread.cpp b/daemon/src/media/video/video_receive_thread.cpp
index 10bb00bb4d..3500d3c4f8 100644
--- a/daemon/src/media/video/video_receive_thread.cpp
+++ b/daemon/src/media/video/video_receive_thread.cpp
@@ -37,6 +37,7 @@
 #include "manager.h"
 #include "client/videomanager.h"
 #include "client/signal.h"
+#include "sinkclient.h"
 #include "logger.h"
 
 #include <unistd.h>
@@ -55,7 +56,7 @@ VideoReceiveThread::VideoReceiveThread(const std::string& id,
     , id_(id)
     , stream_(sdp)
     , sdpContext_(stream_.str().size(), false, &readFunction, 0, 0, this)
-    , sink_(id)
+    , sink_ {Manager::instance().createSinkClient(id)}
     , requestKeyFrameCallback_(0)
     , loop_(std::bind(&VideoReceiveThread::setup, this),
             std::bind(&VideoReceiveThread::process, this),
@@ -128,7 +129,7 @@ bool VideoReceiveThread::setup()
         dstHeight_ = videoDecoder_->getHeight();
     }
 
-    EXIT_IF_FAIL(sink_.start(), "RX: sink startup failed");
+    EXIT_IF_FAIL(sink_->start(), "RX: sink startup failed");
 
     auto conf = Manager::instance().getConferenceFromCallID(id_);
     if (!conf)
@@ -142,9 +143,11 @@ void VideoReceiveThread::process()
 
 void VideoReceiveThread::cleanup()
 {
-    if (detach(&sink_))
-        emitSignal<DRing::VideoSignal::DecodingStopped>(id_, sink_.openedName(), false);
-    sink_.stop();
+    if (detach(sink_.get()))
+        emitSignal<DRing::VideoSignal::DecodingStopped>(id_,
+                                                        sink_->openedName(),
+                                                        false);
+    sink_->stop();
 
     videoDecoder_.reset();
     demuxContext_.reset();
@@ -202,9 +205,11 @@ void VideoReceiveThread::enterConference()
     if (!loop_.isRunning())
         return;
 
-    if (detach(&sink_)) {
-        emitSignal<DRing::VideoSignal::DecodingStopped>(id_, sink_.openedName(), false);
-        RING_DBG("RX: shm sink <%s> detached", sink_.openedName().c_str());
+    if (detach(sink_.get())) {
+        emitSignal<DRing::VideoSignal::DecodingStopped>(id_,
+                                                        sink_->openedName(),
+                                                        false);
+        RING_DBG("RX: shm sink <%s> detached", sink_->openedName().c_str());
     }
 }
 
@@ -214,13 +219,13 @@ void VideoReceiveThread::exitConference()
         return;
 
     if (dstWidth_ > 0 && dstHeight_ > 0) {
-        if (attach(&sink_)) {
+        if (attach(sink_.get())) {
             emitSignal<DRing::VideoSignal::DecodingStarted>(id_,
-                                                            sink_.openedName(),
+                                                            sink_->openedName(),
                                                             dstWidth_,
                                                             dstHeight_, false);
             RING_DBG("RX: shm sink <%s> started: size = %dx%d",
-                  sink_.openedName().c_str(), dstWidth_, dstHeight_);
+                     sink_->openedName().c_str(), dstWidth_, dstHeight_);
         }
     }
 }
diff --git a/daemon/src/media/video/video_receive_thread.h b/daemon/src/media/video/video_receive_thread.h
index 321f4d7f27..e93c15d341 100644
--- a/daemon/src/media/video/video_receive_thread.h
+++ b/daemon/src/media/video/video_receive_thread.h
@@ -32,10 +32,10 @@
 #ifndef _VIDEO_RECEIVE_THREAD_H_
 #define _VIDEO_RECEIVE_THREAD_H_
 
+#include "video_base.h"
 #include "media_codec.h"
 #include "media_io_handle.h"
 #include "media_device.h"
-#include "sinkclient.h"
 #include "threadloop.h"
 #include "noncopyable.h"
 
@@ -52,6 +52,8 @@ class MediaDecoder;
 
 namespace ring { namespace video {
 
+class SinkClient;
+
 class VideoReceiveThread : public VideoGenerator {
 public:
     VideoReceiveThread(const std::string &id, const std::string &sdp);
@@ -83,7 +85,7 @@ private:
     std::istringstream stream_;
     MediaIOHandle sdpContext_;
     std::unique_ptr<MediaIOHandle> demuxContext_;
-    SinkClient sink_;
+    std::shared_ptr<SinkClient> sink_;
     void (*requestKeyFrameCallback_)(const std::string &);
     void openDecoder();
     bool decodeFrame();
-- 
GitLab