[vlc-devel] [PATCH] splitter: use a different thread for each display

Alexandre Janniaux ajanni at videolabs.io
Wed Dec 11 11:32:10 CET 2019


Display modules are expected to present the image to the user from the
display callback. Some display can achieve it asynchronously but others
having VSYNC mechanisms like OpenGL are forcing the display to sleep
until the next VSYNC. In a general way, similar display with a limited
number of render buffer will wait until a new buffer gets released and
available for processing.

While it is correct for one display, the behaviour will make splitter
managed display miss the screen frame deadline and generate stuttering
with more than one output. It stems from display operations being
executed sequentially for each output.

Instead, parallelize and synchronize each display's prepare+display so
that display taking VSYNC into account don't accumulate sleeping time.
---
 modules/video_output/splitter.c | 132 +++++++++++++++++++++++++++++---
 1 file changed, 122 insertions(+), 10 deletions(-)

diff --git a/modules/video_output/splitter.c b/modules/video_output/splitter.c
index 8d6532ceb4..195bf2c50c 100644
--- a/modules/video_output/splitter.c
+++ b/modules/video_output/splitter.c
@@ -34,23 +34,77 @@
 #include <vlc_codec.h>
 #include <vlc_vout_display.h>
 #include <vlc_video_splitter.h>
+#include <vlc_threads.h>
 
 struct vlc_vidsplit_part {
     vout_window_t *window;
     vout_display_t *display;
+    picture_t *picture;
+    vlc_tick_t date;
     vlc_sem_t lock;
     unsigned width;
     unsigned height;
 };
 
+struct vlc_vidsplit_thread
+{
+    vout_display_t *vd;
+    struct vlc_vidsplit_part *part;
+    vlc_thread_t thread;
+};
+
 struct vout_display_sys_t {
     video_splitter_t splitter;
     vlc_mutex_t lock;
 
     picture_t **pictures;
     struct vlc_vidsplit_part *parts;
+    struct vlc_vidsplit_thread *threads;
+
+    vlc_sem_t prepare_wait;
+    vlc_sem_t prepare_done;
+    vlc_sem_t display_wait;
+    vlc_sem_t display_done;
 };
 
+static void* vlc_vidsplit_ThreadDisplay(void *data)
+{
+    struct vlc_vidsplit_thread *vd_thread = data;
+    vout_display_t *vd = vd_thread->vd;
+    vout_display_sys_t *sys = vd->sys;
+    struct vlc_vidsplit_part *part = vd_thread->part;
+
+    for (;;)
+    {
+        vlc_sem_wait(&sys->prepare_wait);
+
+        /* We might need to stop the prepare just after the barrier
+         * and we were not supposed to take the semaphore token. */
+        if (part->display == NULL)
+        {
+            /* If we don't have a display, we can early exist. */
+            vlc_sem_post(&sys->prepare_done);
+            continue;
+        }
+
+        part->picture = vout_display_Prepare(part->display,
+                                             part->picture, NULL,
+                                             part->date);
+
+        /* notify readiness */
+        vlc_sem_post(&sys->prepare_done);
+
+        vlc_sem_wait(&sys->display_wait);
+        if (part->picture)
+            vout_display_Display(part->display, part->picture);
+
+        /* notify that image has been displayed */
+        vlc_sem_post(&sys->display_done);
+    }
+
+    return NULL;
+}
+
 static void vlc_vidsplit_Prepare(vout_display_t *vd, picture_t *pic,
                                  subpicture_t *subpic, vlc_tick_t date)
 {
@@ -69,26 +123,61 @@ static void vlc_vidsplit_Prepare(vout_display_t *vd, picture_t *pic,
     }
     vlc_mutex_unlock(&sys->lock);
 
+    /* After here, part are locked until all vout display has finished
+     * displaying the picture. See vlc_vidsplit_Display. */
+    for (int i = 0; i < sys->splitter.i_output; i++)
+        vlc_sem_wait(&sys->parts[i].lock);
+
+    /* Now that all display are waiting, prepare their state and remove unused
+     * pictures from killed display. */
     for (int i = 0; i < sys->splitter.i_output; i++) {
         struct vlc_vidsplit_part *part = &sys->parts[i];
-
-        vlc_sem_wait(&part->lock);
-        sys->pictures[i] = vout_display_Prepare(part->display,
-                                                sys->pictures[i], NULL, date);
+        part->picture = sys->pictures[i];
+        part->date = date;
+
+        if (part->display == NULL)
+        {
+            /* The display died, cleanup. */
+            part->picture = NULL;
+            if (sys->pictures[i] != NULL)
+            {
+                picture_Release(sys->pictures[i]);
+                sys->pictures[i] = NULL;
+            }
+        }
     }
+
+    /* Start preparing each vout display.  */
+    for (int i = 0; i < sys->splitter.i_output; ++i)
+        if (sys->parts[i].display != NULL)
+            vlc_sem_post(&sys->prepare_wait);
+
+    /* Wait for each vout display to have finished. */
+    for (int i = 0; i < sys->splitter.i_output; ++i)
+        if (sys->parts[i].display != NULL)
+            vlc_sem_wait(&sys->prepare_done);
+
+    /* The time to prepare all vout display is the preparation time for the
+     * slower of all display, which matches the core requirement. */
 }
 
 static void vlc_vidsplit_Display(vout_display_t *vd, picture_t *picture)
 {
     vout_display_sys_t *sys = vd->sys;
 
-    for (int i = 0; i < sys->splitter.i_output; i++) {
-        struct vlc_vidsplit_part *part = &sys->parts[i];
+    /* Request each output to display the picture. */
+    for (int i = 0; i < sys->splitter.i_output; i++)
+        if (sys->parts[i].display != NULL)
+            vlc_sem_post(&sys->display_wait);
 
-        if (sys->pictures[i] != NULL)
-            vout_display_Display(part->display, sys->pictures[i]);
-        vlc_sem_post(&part->lock);
-    }
+    /* Wait until every output has displayed a picture. */
+    for (int i = 0; i < sys->splitter.i_output; i++)
+        if (sys->parts[i].display != NULL)
+            vlc_sem_wait(&sys->display_done);
+
+    /* Release parts lock, we can't read sys->parts[i] after that. */
+    for (int i = 0; i < sys->splitter.i_output; i++)
+        vlc_sem_post(&sys->parts[i].lock);
 
     (void) picture;
 }
@@ -115,6 +204,7 @@ static void vlc_vidsplit_Close(vout_display_t *vd)
 
     for (int i = 0; i < n; i++) {
         struct vlc_vidsplit_part *part = &sys->parts[i];
+        struct vlc_vidsplit_thread *thread = &sys->threads[i];
         vout_display_t *display;
 
         vlc_sem_wait(&part->lock);
@@ -122,6 +212,9 @@ static void vlc_vidsplit_Close(vout_display_t *vd)
         part->display = NULL;
         vlc_sem_post(&part->lock);
 
+        vlc_cancel(thread->thread);
+        vlc_join(thread->thread, NULL);
+
         if (display != NULL)
             vout_display_Delete(display);
 
@@ -259,12 +352,19 @@ static int vlc_vidsplit_Open(vout_display_t *vd,
                                         * sizeof (*sys->pictures));
     sys->parts = vlc_obj_malloc(obj,
                                 splitter->i_output * sizeof (*sys->parts));
+    sys->threads = vlc_obj_malloc(obj,
+                                  splitter->i_output * sizeof (*sys->threads));
     if (unlikely(sys->pictures == NULL || sys->parts == NULL)) {
         splitter->i_output = 0;
         vlc_vidsplit_Close(vd);
         return VLC_ENOMEM;
     }
 
+    vlc_sem_init(&sys->prepare_wait, 0);
+    vlc_sem_init(&sys->prepare_done, 0);
+    vlc_sem_init(&sys->display_wait, 0);
+    vlc_sem_init(&sys->display_done, 0);
+
     for (int i = 0; i < splitter->i_output; i++) {
         const video_splitter_output_t *output = &splitter->p_output[i];
         vout_display_cfg_t vdcfg = {
@@ -305,6 +405,18 @@ static int vlc_vidsplit_Open(vout_display_t *vd,
         part->display = display;
         vout_display_SetSize(display, part->width, part->height);
         vlc_sem_post(&part->lock);
+
+        sys->threads[i].vd = vd;
+        sys->threads[i].part = part;
+
+        int ret_clone = vlc_clone(&sys->threads[i].thread,
+                                  vlc_vidsplit_ThreadDisplay,
+                                  &sys->threads[i],
+                                  VLC_THREAD_PRIORITY_VIDEO);
+
+        if (ret_clone != VLC_SUCCESS) {
+            /* TODO: Abort and cleanup */
+        }
     }
 
     vd->prepare = vlc_vidsplit_Prepare;
-- 
2.24.1



More information about the vlc-devel mailing list