From 15e133301eae6fff7354490d453b2446fd16a7a7 Mon Sep 17 00:00:00 2001
From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
Date: Mon, 26 Sep 2022 21:01:45 -0400
Subject: [PATCH] avmodel: use synchronous callbacks for
 DecodingStarted/Stopped

This is an older issue that has resurfaced where mobile device rotation
at the beginning of a call cause a frame-copy to a stale buffer because
the DecodingStarted event is handled asynchronously.

Noticed on Windows but I believe any non-dbus build should have it.

So we make all the connections blocking and adjust some parameters.
This commit also removes the DecodingStarted handler in CallModel which
was causing the client's target video frame to be reallocated for each
account present.

Change-Id: I23ac4e0bd4b446e7a532f0d362f7ecd209d3c790
GitLab: #536
---
 src/app/avadapter.cpp              |  3 +-
 src/app/avadapter.h                |  2 +-
 src/app/videoprovider.cpp          | 32 ++++++++-----
 src/app/videoprovider.h            |  2 +-
 src/libclient/api/avmodel.h        |  3 +-
 src/libclient/avmodel.cpp          | 76 +++++++++++++++---------------
 src/libclient/callbackshandler.cpp |  4 +-
 src/libclient/callmodel.cpp        | 37 +++++----------
 src/libclient/directrenderer.cpp   |  2 +-
 src/libclient/renderer.h           |  2 +-
 src/libclient/shmrenderer.cpp      |  2 +-
 11 files changed, 79 insertions(+), 86 deletions(-)

diff --git a/src/app/avadapter.cpp b/src/app/avadapter.cpp
index 8716decc3..c33da4723 100644
--- a/src/app/avadapter.cpp
+++ b/src/app/avadapter.cpp
@@ -298,8 +298,9 @@ AvAdapter::onAudioDeviceEvent()
 }
 
 void
-AvAdapter::onRendererStarted(const QString& id)
+AvAdapter::onRendererStarted(const QString& id, const QSize& size)
 {
+    Q_UNUSED(size)
     auto callId = lrcInstance_->getCurrentCallId();
     auto callModel = lrcInstance_->getCurrentCallModel();
     auto renderDevice = callModel->getCurrentRenderedDevice(callId);
diff --git a/src/app/avadapter.h b/src/app/avadapter.h
index 793a6f438..f565ce59a 100644
--- a/src/app/avadapter.h
+++ b/src/app/avadapter.h
@@ -108,7 +108,7 @@ protected:
 
 private Q_SLOTS:
     void onAudioDeviceEvent();
-    void onRendererStarted(const QString& id);
+    void onRendererStarted(const QString& id, const QSize& size);
 
 private:
     // Get screens arrangement rect relative to primary screen.
diff --git a/src/app/videoprovider.cpp b/src/app/videoprovider.cpp
index 127a89662..ac9ccde79 100644
--- a/src/app/videoprovider.cpp
+++ b/src/app/videoprovider.cpp
@@ -64,9 +64,17 @@ VideoProvider::registerSink(const QString& id, QVideoSink* obj)
     if (it == framesObjects_.end()) {
         auto fo = std::make_unique<FrameObject>();
         fo->subscribers.insert(obj);
-        framesObjects_.emplace(id, std::move(fo));
-        return;
+        qDebug() << "Creating new FrameObject for id:" << id;
+        auto emplaced = framesObjects_.emplace(id, std::move(fo));
+        if (!emplaced.second) {
+            qWarning() << "Couldn't create FrameObject for id:" << id;
+            return;
+        }
+        it = emplaced.first;
     }
+    qDebug().noquote() << QString("Adding sink: 0x%1 to subscribers for id: %2")
+                              .arg((quintptr) obj, QT_POINTER_SIZE * 2, 16, QChar('0'))
+                              .arg(id);
     it->second->subscribers.insert(obj);
 }
 
@@ -78,7 +86,11 @@ VideoProvider::unregisterSink(QVideoSink* obj)
         auto& subs = frameObjIt.second->subscribers;
         auto it = subs.constFind(obj);
         if (it != subs.cend()) {
+            qDebug().noquote() << QString("Removing sink: 0x%1 from subscribers for id: %2")
+                                      .arg((quintptr) obj, QT_POINTER_SIZE * 2, 16, QChar('0'))
+                                      .arg(frameObjIt.first);
             subs.erase(it);
+            return;
         }
     }
 }
@@ -121,15 +133,11 @@ VideoProvider::captureVideoFrame(const QString& id)
 }
 
 void
-VideoProvider::onRendererStarted(const QString& id)
+VideoProvider::onRendererStarted(const QString& id, const QSize& size)
 {
-    auto size = avModel_.getRendererSize(id);
-    // This slot is queued, the renderer may have been destroyed.
-    if (size.width() == 0 || size.height() == 0 || activeRenderers_[id] == size) {
-        return;
-    }
-    auto pixelFormat = avModel_.useDirectRenderer() ? QVideoFrameFormat::Format_RGBA8888
-                                                    : QVideoFrameFormat::Format_BGRA8888;
+    static const auto pixelFormat = avModel_.useDirectRenderer()
+                                        ? QVideoFrameFormat::Format_RGBA8888
+                                        : QVideoFrameFormat::Format_BGRA8888;
     auto frameFormat = QVideoFrameFormat(size, pixelFormat);
     {
         QMutexLocker lk(&framesObjsMutex_);
@@ -137,11 +145,11 @@ VideoProvider::onRendererStarted(const QString& id)
         if (it == framesObjects_.end()) {
             auto fo = std::make_unique<FrameObject>();
             fo->videoFrame = std::make_unique<QVideoFrame>(frameFormat);
-            qDebug() << "Create new QVideoFrame " << frameFormat.frameSize();
+            qDebug() << "Create new QVideoFrame" << frameFormat.frameSize();
             framesObjects_.emplace(id, std::move(fo));
         } else {
             it->second->videoFrame.reset(new QVideoFrame(frameFormat));
-            qDebug() << "QVideoFrame reset to " << frameFormat.frameSize();
+            qDebug() << "QVideoFrame reset to" << frameFormat.frameSize();
         }
     }
 
diff --git a/src/app/videoprovider.h b/src/app/videoprovider.h
index c2358a80a..775e42500 100644
--- a/src/app/videoprovider.h
+++ b/src/app/videoprovider.h
@@ -48,7 +48,7 @@ public:
     Q_INVOKABLE QString captureVideoFrame(const QString& id);
 
 private Q_SLOTS:
-    void onRendererStarted(const QString& id);
+    void onRendererStarted(const QString& id, const QSize& size);
     void onFrameBufferRequested(const QString& id, AVFrame* avframe);
     void onFrameUpdated(const QString& id);
     void onRendererStopped(const QString& id);
diff --git a/src/libclient/api/avmodel.h b/src/libclient/api/avmodel.h
index 5bcdc5dc4..d0ab442e2 100644
--- a/src/libclient/api/avmodel.h
+++ b/src/libclient/api/avmodel.h
@@ -281,8 +281,9 @@ Q_SIGNALS:
     /**
      * Emitted when a renderer is started
      * @param id of the renderer
+     * @param size of the renderer
      */
-    void rendererStarted(const QString& id);
+    void rendererStarted(const QString& id, const QSize& size);
     /**
      * Emitted when a renderer is stopped
      * @param id of the renderer
diff --git a/src/libclient/avmodel.cpp b/src/libclient/avmodel.cpp
index 02b8e3f13..dacaf1e07 100644
--- a/src/libclient/avmodel.cpp
+++ b/src/libclient/avmodel.cpp
@@ -694,11 +694,13 @@ AVModelPimpl::AVModelPimpl(AVModel& linked, const CallbacksHandler& callbacksHan
     connect(&callbacksHandler,
             &CallbacksHandler::decodingStarted,
             this,
-            &AVModelPimpl::onDecodingStarted);
+            &AVModelPimpl::onDecodingStarted,
+            Qt::DirectConnection);
     connect(&callbacksHandler,
             &CallbacksHandler::decodingStopped,
             this,
-            &AVModelPimpl::onDecodingStopped);
+            &AVModelPimpl::onDecodingStopped,
+            Qt::DirectConnection);
 
     auto startedPreview = false;
     auto restartRenderers = [&](const QStringList& callList) {
@@ -750,8 +752,6 @@ AVModelPimpl::getRecordingPath() const
 void
 AVModelPimpl::onDecodingStarted(const QString& id, const QString& shmPath, int width, int height)
 {
-    if (id != "" && id != "local" && !id.contains("://")) // Else managed by callmodel
-        return;
     addRenderer(id, QSize(width, height), shmPath);
 }
 
@@ -824,41 +824,39 @@ createRenderer(const QString& id, const QSize& res, const QString& shmPath = {})
 void
 AVModelPimpl::addRenderer(const QString& id, const QSize& res, const QString& shmPath)
 {
-    {
-        auto connectRenderer = [this](Renderer* renderer, const QString& id) {
-            connect(
-                renderer,
-                &Renderer::started,
-                this,
-                [this, id] { Q_EMIT linked_.rendererStarted(id); },
-                Qt::QueuedConnection);
-            connect(
-                renderer,
-                &Renderer::frameBufferRequested,
-                this,
-                [this, id](AVFrame* frame) { Q_EMIT linked_.frameBufferRequested(id, frame); },
-                Qt::DirectConnection);
-            connect(
-                renderer,
-                &Renderer::frameUpdated,
-                this,
-                [this, id] { Q_EMIT linked_.frameUpdated(id); },
-                Qt::DirectConnection);
-            connect(
-                renderer,
-                &Renderer::stopped,
-                this,
-                [this, id] { Q_EMIT linked_.rendererStopped(id); },
-                Qt::DirectConnection);
-        };
-        std::lock_guard<std::mutex> lk(renderers_mtx_);
-        renderers_.erase(id); // Because it should be done before creating the renderer
-        auto renderer = createRenderer(id, res, shmPath);
-        auto& r = renderers_[id];
-        r = std::move(renderer);
-        connectRenderer(r.get(), id);
-        r->startRendering();
-    }
+    auto connectRenderer = [this](Renderer* renderer, const QString& id) {
+        connect(
+            renderer,
+            &Renderer::started,
+            this,
+            [this, id](const QSize& size) { Q_EMIT linked_.rendererStarted(id, size); },
+            Qt::DirectConnection);
+        connect(
+            renderer,
+            &Renderer::frameBufferRequested,
+            this,
+            [this, id](AVFrame* frame) { Q_EMIT linked_.frameBufferRequested(id, frame); },
+            Qt::DirectConnection);
+        connect(
+            renderer,
+            &Renderer::frameUpdated,
+            this,
+            [this, id] { Q_EMIT linked_.frameUpdated(id); },
+            Qt::DirectConnection);
+        connect(
+            renderer,
+            &Renderer::stopped,
+            this,
+            [this, id] { Q_EMIT linked_.rendererStopped(id); },
+            Qt::DirectConnection);
+    };
+    std::lock_guard<std::mutex> lk(renderers_mtx_);
+    renderers_.erase(id); // Because it should be done before creating the renderer
+    auto renderer = createRenderer(id, res, shmPath);
+    auto& r = renderers_[id];
+    r = std::move(renderer);
+    connectRenderer(r.get(), id);
+    r->startRendering();
 }
 
 void
diff --git a/src/libclient/callbackshandler.cpp b/src/libclient/callbackshandler.cpp
index 6a93d5124..b0e085d14 100644
--- a/src/libclient/callbackshandler.cpp
+++ b/src/libclient/callbackshandler.cpp
@@ -275,13 +275,13 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
             &VideoManagerInterface::decodingStarted,
             this,
             &CallbacksHandler::decodingStarted,
-            Qt::QueuedConnection);
+            Qt::DirectConnection);
 
     connect(&VideoManager::instance(),
             &VideoManagerInterface::decodingStopped,
             this,
             &CallbacksHandler::decodingStopped,
-            Qt::QueuedConnection);
+            Qt::DirectConnection);
 
     connect(&VideoManager::instance(),
             &VideoManagerInterface::deviceEvent,
diff --git a/src/libclient/callmodel.cpp b/src/libclient/callmodel.cpp
index 93afd5419..baf1a19aa 100644
--- a/src/libclient/callmodel.cpp
+++ b/src/libclient/callmodel.cpp
@@ -253,14 +253,6 @@ public Q_SLOTS:
      * @param state the new state
      */
     void remoteRecordingChanged(const QString& callId, const QString& peerNumber, bool state);
-    /**
-     * Listen from CallbacksHandler when a renderer starts
-     * @param id
-     * @param shmPath
-     * @param width
-     * @param height
-     */
-    void onDecodingStarted(const QString& id, const QString& shmPath, int width, int height);
 };
 
 CallModel::CallModel(const account::Info& owner,
@@ -525,7 +517,7 @@ CallModel::addMedia(const QString& callId, const QString& source, MediaRequestTy
     CallManager::instance().requestMediaChange(owner.id, callId, proposedList);
     callInfo->mediaList = proposedList;
     if (callInfo->status == call::Status::IN_PROGRESS)
-            Q_EMIT callInfosChanged(owner.id, callId);
+        Q_EMIT callInfosChanged(owner.id, callId);
 }
 
 void
@@ -578,12 +570,15 @@ CallModel::removeMedia(const QString& callId,
         return;
     } else if (!hasVideo) {
         // To receive the remote video, we need a muted camera
-        proposedList.push_back(
-            MapStringString {{MediaAttributeKey::MEDIA_TYPE, MediaAttributeValue::VIDEO},
-                             {MediaAttributeKey::ENABLED, TRUE_STR},
-                             {MediaAttributeKey::MUTED, TRUE_STR},
-                             {MediaAttributeKey::SOURCE, pimpl_->lrc.getAVModel().getCurrentVideoCaptureDevice()}, // not needed to set the source. Daemon should be able to check it
-                             {MediaAttributeKey::LABEL, label.isEmpty() ? "video_0" : label}});
+        proposedList.push_back(MapStringString {
+            {MediaAttributeKey::MEDIA_TYPE, MediaAttributeValue::VIDEO},
+            {MediaAttributeKey::ENABLED, TRUE_STR},
+            {MediaAttributeKey::MUTED, TRUE_STR},
+            {MediaAttributeKey::SOURCE,
+             pimpl_->lrc.getAVModel()
+                 .getCurrentVideoCaptureDevice()}, // not needed to set the source. Daemon should be
+                                                   // able to check it
+            {MediaAttributeKey::LABEL, label.isEmpty() ? "video_0" : label}});
     }
 
     if (isVideo && !label.isEmpty())
@@ -592,7 +587,7 @@ CallModel::removeMedia(const QString& callId,
     CallManager::instance().requestMediaChange(owner.id, callId, proposedList);
     callInfo->mediaList = proposedList;
     if (callInfo->status == call::Status::IN_PROGRESS)
-            Q_EMIT callInfosChanged(owner.id, callId);
+        Q_EMIT callInfosChanged(owner.id, callId);
 }
 
 void
@@ -945,10 +940,6 @@ CallModelPimpl::CallModelPimpl(const CallModel& linked,
             &CallbacksHandler::remoteRecordingChanged,
             this,
             &CallModelPimpl::remoteRecordingChanged);
-    connect(&callbacksHandler,
-            &CallbacksHandler::decodingStarted,
-            this,
-            &CallModelPimpl::onDecodingStarted);
 
 #ifndef ENABLE_LIBWRAP
     // Only necessary with dbus since the daemon runs separately
@@ -1644,12 +1635,6 @@ CallModelPimpl::remoteRecordingChanged(const QString& callId, const QString& pee
     Q_EMIT linked.remoteRecordingChanged(callId, it->second->peerRec, state);
 }
 
-void
-CallModelPimpl::onDecodingStarted(const QString& id, const QString& shmPath, int width, int height)
-{
-    lrc.getAVModel().addRenderer(id, QSize(width, height), shmPath);
-}
-
 } // namespace lrc
 
 #include "api/moc_callmodel.cpp"
diff --git a/src/libclient/directrenderer.cpp b/src/libclient/directrenderer.cpp
index 51209d9b3..6af6d3ba1 100644
--- a/src/libclient/directrenderer.cpp
+++ b/src/libclient/directrenderer.cpp
@@ -104,7 +104,7 @@ DirectRenderer::~DirectRenderer() {}
 void
 DirectRenderer::startRendering()
 {
-    Q_EMIT started();
+    Q_EMIT started(size());
 }
 
 void
diff --git a/src/libclient/renderer.h b/src/libclient/renderer.h
index 7cba0f9ef..df7419768 100644
--- a/src/libclient/renderer.h
+++ b/src/libclient/renderer.h
@@ -61,7 +61,7 @@ public Q_SLOTS:
 Q_SIGNALS:
     void frameUpdated();
     void stopped();
-    void started();
+    void started(const QSize& size);
     void frameBufferRequested(AVFrame* avFrame);
 
 private:
diff --git a/src/libclient/shmrenderer.cpp b/src/libclient/shmrenderer.cpp
index bdd4eee25..0a2f9b892 100644
--- a/src/libclient/shmrenderer.cpp
+++ b/src/libclient/shmrenderer.cpp
@@ -328,7 +328,7 @@ ShmRenderer::startRendering()
 
     pimpl_->timer->start();
 
-    Q_EMIT started();
+    Q_EMIT started(size());
 }
 
 // Done on destroy instead
-- 
GitLab