From baf125519ec6ee7c22f3c4a415d4ce528f032147 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Thu, 20 Aug 2020 16:08:47 -0400
Subject: [PATCH] conference: handle participants without video

Conferences informations were missing two cases:
1. Somebody in the conference, but without any video session. This participant
is only present in the conference object and need to be added in the informations
sent.
2. A participant with a video output, but no input (video muted). In this case,
the video is rendered by the video mixer, but with a black frame. The coordinates
should be added into the infos.

To do that, the position calculation is done outside the render_frame and
ParticipantInfos now has a videoMuted and audioMuted (not used for now) field

Change-Id: I0b979f99c9db032dccbbc8a2cd1a14125ef72071
---
 src/conference.cpp              |  17 +++--
 src/conference.h                |  10 ++-
 src/media/video/video_mixer.cpp | 124 ++++++++++++++++++--------------
 src/media/video/video_mixer.h   |   7 +-
 4 files changed, 95 insertions(+), 63 deletions(-)

diff --git a/src/conference.cpp b/src/conference.cpp
index 5cb75ecd9f..d44e4edc47 100644
--- a/src/conference.cpp
+++ b/src/conference.cpp
@@ -54,6 +54,8 @@ Conference::Conference()
             if (!shared)
                 return;
             ConfInfo newInfo;
+            auto subCalls = shared->participants_;
+            // Handle participants showing their video
             std::unique_lock<std::mutex> lk(shared->videoToCallMtx_);
             for (const auto& info : infos) {
                 std::string uri = "";
@@ -67,9 +69,8 @@ Conference::Conference()
                     // a master of a conference and there is only one remote
                     // In the future, we should retrieve confInfo from the call
                     // To merge layouts informations
-                    if (auto call = Manager::instance().callFactory.getCall<SIPCall>(it->second)) {
+                    if (auto call = Manager::instance().callFactory.getCall<SIPCall>(it->second))
                         uri = call->getPeerNumber();
-                    }
                 }
                 auto active = false;
                 if (auto videoMixer = shared->getVideoMixer())
@@ -77,10 +78,18 @@ Conference::Conference()
                              or (uri.empty()
                                  and not videoMixer->getActiveParticipant()); // by default, local
                                                                               // is shown as active
-                newInfo.emplace_back(
-                    ParticipantInfo {std::move(uri), active, info.x, info.y, info.w, info.h});
+                subCalls.erase(it->second);
+                newInfo.emplace_back(ParticipantInfo {
+                    std::move(uri), active, info.x, info.y, info.w, info.h, !info.hasVideo, false});
             }
             lk.unlock();
+            // Handle participants not present in the video mixer
+            for (const auto& subCall : subCalls) {
+                std::string uri = "";
+                if (auto call = Manager::instance().callFactory.getCall<SIPCall>(subCall))
+                    uri = call->getPeerNumber();
+                ParticipantInfo {std::move(uri), false, 0, 0, 0, 0, true, false};
+            }
 
             {
                 std::lock_guard<std::mutex> lk2(shared->confInfoMutex_);
diff --git a/src/conference.h b/src/conference.h
index 78dd62a693..68743780b0 100644
--- a/src/conference.h
+++ b/src/conference.h
@@ -49,6 +49,8 @@ struct ParticipantInfo
     int y {0};
     int w {0};
     int h {0};
+    bool videoMuted {false};
+    bool audioMuted {false};
 
     void fromJson(const Json::Value& v)
     {
@@ -58,6 +60,8 @@ struct ParticipantInfo
         y = v["y"].asInt();
         w = v["w"].asInt();
         h = v["h"].asInt();
+        videoMuted = v["videoMuted"].asBool();
+        audioMuted = v["audioMuted"].asBool();
     }
 
     Json::Value toJson() const
@@ -69,6 +73,8 @@ struct ParticipantInfo
         val["y"] = y;
         val["w"] = w;
         val["h"] = h;
+        val["videoMuted"] = videoMuted;
+        val["audioMuted"] = audioMuted;
         return val;
     }
 
@@ -79,7 +85,9 @@ struct ParticipantInfo
                 {"x", std::to_string(x)},
                 {"y", std::to_string(y)},
                 {"w", std::to_string(w)},
-                {"h", std::to_string(h)}};
+                {"h", std::to_string(h)},
+                {"videoMuted", videoMuted ? "true" : "false"},
+                {"audioMuted", audioMuted ? "true" : "false"}};
     }
 };
 
diff --git a/src/media/video/video_mixer.cpp b/src/media/video/video_mixer.cpp
index 92080854ca..0c896bbe83 100644
--- a/src/media/video/video_mixer.cpp
+++ b/src/media/video/video_mixer.cpp
@@ -42,6 +42,9 @@ extern "C" {
 #include <libavutil/display.h>
 }
 
+static constexpr auto MIN_LINE_ZOOM
+    = 6; // Used by the ONE_BIG_WITH_SMALL layout for the small previews
+
 namespace jami {
 namespace video {
 
@@ -63,6 +66,7 @@ struct VideoMixer::VideoMixerSource
     int y {};
     int w {};
     int h {};
+    bool hasVideo {false};
 
 private:
     std::mutex mutex_;
@@ -212,14 +216,13 @@ VideoMixer::process()
         int i = 0;
         bool activeFound = false;
         bool needsUpdate = layoutUpdated_ > 0;
-        bool successfullyRendered = true;
+        bool successfullyRendered = false;
         for (auto& x : sources_) {
             /* thread stop pending? */
             if (!loop_.isRunning())
                 return;
 
-            if (currentLayout_ != Layout::ONE_BIG or activeSource_ == x->source
-                or not activeFound /* By default ONE_BIG will show the first source */) {
+            if (currentLayout_ != Layout::ONE_BIG or activeSource_ == x->source) {
                 // make rendered frame temporarily unavailable for update()
                 // to avoid concurrent access.
                 std::unique_ptr<VideoFrame> input;
@@ -238,17 +241,25 @@ VideoMixer::process()
                     }
                 }
 
+                if (needsUpdate)
+                    calc_position(x, wantedIndex);
+
                 if (input)
-                    successfullyRendered &= render_frame(output, *input, x, wantedIndex, needsUpdate);
-                else
-                    successfullyRendered = false;
+                    successfullyRendered |= render_frame(output, *input, x);
 
+                auto hasVideo = x->hasVideo;
+                x->hasVideo = input && successfullyRendered;
+                if (hasVideo != x->hasVideo) {
+                    layoutUpdated_ += 1;
+                    needsUpdate = true;
+                }
                 x->atomic_swap_render(input);
             } else if (needsUpdate) {
                 x->x = 0;
                 x->y = 0;
                 x->w = 0;
                 x->h = 0;
+                x->hasVideo = false;
             }
 
             ++i;
@@ -259,7 +270,8 @@ VideoMixer::process()
                 std::vector<SourceInfo> sourcesInfo;
                 sourcesInfo.reserve(sources_.size());
                 for (auto& x : sources_) {
-                    sourcesInfo.emplace_back(SourceInfo {x->source, x->x, x->y, x->w, x->h});
+                    sourcesInfo.emplace_back(
+                        SourceInfo {x->source, x->x, x->y, x->w, x->h, x->hasVideo});
                 }
                 if (onSourcesUpdated_)
                     (onSourcesUpdated_)(std::move(sourcesInfo));
@@ -273,9 +285,7 @@ VideoMixer::process()
 bool
 VideoMixer::render_frame(VideoFrame& output,
                          const VideoFrame& input,
-                         std::unique_ptr<VideoMixerSource>& source,
-                         int index,
-                         bool needsUpdate)
+                         std::unique_ptr<VideoMixerSource>& source)
 {
     if (!width_ or !height_ or !input.pointer() or input.pointer()->format == -1)
         return false;
@@ -286,51 +296,10 @@ VideoMixer::render_frame(VideoFrame& output,
     std::shared_ptr<VideoFrame> frame = input;
 #endif
 
-    int cell_width, cell_height, xoff, yoff;
-    if (not needsUpdate) {
-        cell_width = source->w;
-        cell_height = source->h;
-        xoff = source->x;
-        yoff = source->y;
-    } else {
-        const int n = currentLayout_ == Layout::ONE_BIG ? 1 : sources_.size();
-        const int zoom = currentLayout_ == Layout::ONE_BIG_WITH_SMALL ? std::max(6, n)
-                                                                      : ceil(sqrt(n));
-        if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL && index == 0) {
-            // In ONE_BIG_WITH_SMALL, the first line at the top is the previews
-            // The rest is the active source
-            cell_width = width_;
-            cell_height = height_ - height_ / zoom;
-        } else {
-            cell_width = width_ / zoom;
-            cell_height = height_ / zoom;
-        }
-        if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL) {
-            if (index == 0) {
-                xoff = 0;
-                yoff = height_ / zoom; // First line height
-            } else {
-                xoff = (index - 1) * cell_width;
-                // Show sources in center
-                xoff += (width_ - (n - 1) * cell_width) / 2;
-                yoff = 0;
-            }
-        } else {
-            xoff = (index % zoom) * cell_width;
-            if (currentLayout_ == Layout::GRID && n % zoom != 0
-                && index >= (zoom * ((n - 1) / zoom))) {
-                // Last line, center participants if not full
-                xoff += (width_ - (n % zoom) * cell_width) / 2;
-            }
-            yoff = (index / zoom) * cell_height;
-        }
-
-        // Update source's cache
-        source->w = cell_width;
-        source->h = cell_height;
-        source->x = xoff;
-        source->y = yoff;
-    }
+    int cell_width = source->w;
+    int cell_height = source->h;
+    int xoff = source->x;
+    int yoff = source->y;
 
     AVFrameSideData* sideData = av_frame_get_side_data(frame->pointer(),
                                                        AV_FRAME_DATA_DISPLAYMATRIX);
@@ -359,6 +328,51 @@ VideoMixer::render_frame(VideoFrame& output,
     return true;
 }
 
+void
+VideoMixer::calc_position(std::unique_ptr<VideoMixerSource>& source, int index)
+{
+    if (!width_ or !height_)
+        return;
+
+    int cell_width, cell_height, xoff, yoff;
+    const int n = currentLayout_ == Layout::ONE_BIG ? 1 : sources_.size();
+    const int zoom = currentLayout_ == Layout::ONE_BIG_WITH_SMALL ? std::max(MIN_LINE_ZOOM, n)
+                                                                  : ceil(sqrt(n));
+    if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL && index == 0) {
+        // In ONE_BIG_WITH_SMALL, the first line at the top is the previews
+        // The rest is the active source
+        cell_width = width_;
+        cell_height = height_ - height_ / zoom;
+    } else {
+        cell_width = width_ / zoom;
+        cell_height = height_ / zoom;
+    }
+    if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL) {
+        if (index == 0) {
+            xoff = 0;
+            yoff = height_ / zoom; // First line height
+        } else {
+            xoff = (index - 1) * cell_width;
+            // Show sources in center
+            xoff += (width_ - (n - 1) * cell_width) / 2;
+            yoff = 0;
+        }
+    } else {
+        xoff = (index % zoom) * cell_width;
+        if (currentLayout_ == Layout::GRID && n % zoom != 0 && index >= (zoom * ((n - 1) / zoom))) {
+            // Last line, center participants if not full
+            xoff += (width_ - (n % zoom) * cell_width) / 2;
+        }
+        yoff = (index / zoom) * cell_height;
+    }
+
+    // Update source's cache
+    source->w = cell_width;
+    source->h = cell_height;
+    source->x = xoff;
+    source->y = yoff;
+}
+
 void
 VideoMixer::setParameters(int width, int height, AVPixelFormat format)
 {
diff --git a/src/media/video/video_mixer.h b/src/media/video/video_mixer.h
index 9710165db5..c0401d70ba 100644
--- a/src/media/video/video_mixer.h
+++ b/src/media/video/video_mixer.h
@@ -43,6 +43,7 @@ struct SourceInfo
     int y;
     int w;
     int h;
+    bool hasVideo;
 };
 using OnSourcesUpdatedCb = std::function<void(const std::vector<SourceInfo>&&)>;
 
@@ -87,9 +88,9 @@ private:
 
     bool render_frame(VideoFrame& output,
                       const VideoFrame& input,
-                      std::unique_ptr<VideoMixerSource>& source,
-                      int index,
-                      bool needsUpdate);
+                      std::unique_ptr<VideoMixerSource>& source);
+
+    void calc_position(std::unique_ptr<VideoMixerSource>& source, int index);
 
     void start_sink();
     void stop_sink();
-- 
GitLab