[vlc-commits] [Git][videolan/vlc][master] 17 commits: test: add vlc_input_decoder test

Steve Lhomme (@robUx4) gitlab at videolan.org
Mon Sep 12 07:08:14 UTC 2022



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
a6c3e3c6 by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
test: add vlc_input_decoder test

The test provides an infrastructure based on previous transcode tests
and video output tests, that will serve as basis for other
input_decoder.c tests.

- - - - -
3dd0f548 by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
input: decoder: use vlc_input_decoder_Flush for CC

Separate the early return to make it clear that what follows require a
valid closed-caption decoder.

Then, use vlc_input_decoder_Flush directly, because pf_flush must be
called from the CC decoder thread, and the fifo must be flushed
beforehand, instead of calling it directly.

- - - - -
ac7f9ce2 by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
input: decoder: add cc.lock for sub-decoders

Sub-decoders are decoders whose state is defined by previous decoders.
Specifically in the current case, picture frames with specific SEI data
can convey subtitles as closed captions and report them from the decoder
implementation itself.

Since the core never locks the implementation, and the implementation
can be asynchronously reporting subtitles, a common lock need to be
setup so that the core can then check and use the sub-decoders being
created without racing against the upper decoder implementation.

Co-authored-by: François Cartegnie <fcvlcdev at free.fr>

- - - - -
8f7b3838 by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
input: decoder: fix locking CC

vlc_input_decoder_HasCCChanFlag also needs to be protected, and the
owner->lock mutex will be removed in later patches, to be completely
replaced by the already existing fifo lock.

The new cc.lock allows better thread safety without risking a deadlock
between the super-decoder and the sub-decoder, by protecting the very
state being synced by both of them.

- - - - -
2e41d0c6 by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
test: input_decoder: add CC tests

The test is reliably failing with thread sanitizer enabled, without the
previous patches:

    ==================
    WARNING: ThreadSanitizer: data race (pid=242170)
      Write of size 8 at 0x7b6c00010088 by thread T10:
        #0 DecoderPlayCc ../../src/input/decoder.c:1008 (libvlccore.so.9+0xebbf5)
        #1 ModuleThread_QueueCc ../../src/input/decoder.c:1064 (libvlccore.so.9+0xec031)
        #2 decoder_QueueCc ../../include/vlc_codec.h:444 (test_src_input_decoder+0x5768)
        #3 decoder_decode_check_cc ../../test/src/input/decoder/input_decoder_scenarios.c:79 (test_src_input_decoder+0x5a4e)
        #4 DecoderDecode ../../test/src/input/decoder/input_decoder.c:90 (test_src_input_decoder+0x3dd4)
        #5 DecoderThread_DecodeBlock ../../src/input/decoder.c:1376 (libvlccore.so.9+0xed9f1)
        #6 DecoderThread_ProcessInput ../../src/input/decoder.c:1498 (libvlccore.so.9+0xee14a)
        #7 DecoderThread ../../src/input/decoder.c:1786 (libvlccore.so.9+0xef877)

      Previous read of size 1 at 0x7b6c00010088 by thread T8:
        #0 vlc_input_decoder_HasCCChanFlag ../../src/input/decoder.c:2445 (libvlccore.so.9+0xf2a2b)
        #1 vlc_input_decoder_SetCcState ../../src/input/decoder.c:2464 (libvlccore.so.9+0xf2b5f)
        #2 EsOutSelectEs ../../src/input/es_out.c:2446 (libvlccore.so.9+0x1095c4)
        #3 EsOutSelect ../../src/input/es_out.c:2686 (libvlccore.so.9+0x10a7fc)
        #4 EsOutVaControlLocked ../../src/input/es_out.c:3270 (libvlccore.so.9+0x10dea5)
        #5 EsOutControlLocked ../../src/input/es_out.c:3147 (libvlccore.so.9+0x10d0ae)
        #6 EsOutVaPrivControlLocked ../../src/input/es_out.c:3759 (libvlccore.so.9+0x112282)
        #7 EsOutPrivControl ../../src/input/es_out.c:4028 (libvlccore.so.9+0x11505c)
        #8 es_out_vaPrivControl ../../src/input/es_out.h:105 (libvlccore.so.9+0x125a49)
        #9 es_out_PrivControl ../../src/input/es_out.h:112 (libvlccore.so.9+0x125b38)
        #10 es_out_SetEs ../../src/input/es_out.h:124 (libvlccore.so.9+0x125c4b)
        #11 Control ../../src/input/input.c:2123 (libvlccore.so.9+0x130e15)
        #12 MainLoop ../../src/input/input.c:724 (libvlccore.so.9+0x129676)
        #13 Run ../../src/input/input.c:428 (libvlccore.so.9+0x127faa)

- - - - -
322ea200 by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
vlc_frame: add vlc_fifo_Held/Assert

vlc_fifo_t are coming with their own lock which is exposed on the public
interface, so provide sanitization state check functions like those
available for vlc_mutex_t.

- - - - -
2a4d648e by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
input: decoder: extract substream handling

The substream handling was under a lot of precondition, leading to a lot
of indentation. Moving this handling into a separate function allows
early return within the function, which reduce to a single indentation
level and greatly simplify the reading of the function while simplifying
the caller site where it was only a specific case to handle.

- - - - -
e5b48bc7 by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
input: decoder: extract locking

The default case is supposed to be unreachable.

- - - - -
7dcc370d by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
input: decoder: remove incorrectly ordered locking

The input decoder component is made of three different states:

 - Lock A:
   The input_decoder itself, loading the decoder and protected against
   concurrent usage of the decoder through p_owner->lock.

 - Lock B:
   The decoder implementation, that might create internal lock or
   synchronization object to process decoding asynchronously.

 - Lock C:
   The decoder implementation output, or the "owner" part viewed by the
   decoder implementation, which needs to be protected against access
   from the decoder and access from the input_decoder and is protected
   through the fifo lock, also protecting the fifo in which input data
   is pushed.

Because the decoder implementation is protected by the p_owner->lock (or
here, lock A), we can never lock A from the decoder implementation,
which is what the previous code was doing.

Likewise, since the decoder implementation will use the output to queue
picture and signal state changes, it must take Lock C and thus the
output part can never take either A or B.

Instead, we enforce the order:

                  Lock A -> Lock B -> Lock C:

Which means that:

 - The decoder implementation can lock the input decoder output (C) to
   push new changes.

 - The input_decoder can lock its output (C) directly to affect what
   state the decoder implementation will see when queueing changes.

 - The input_decoder will only lock A to protect the decoder_t object
   from being used concurrently.

This fixes deadlock in specific conditions where an asynchronous decoder
implementation would queue a picture with Lock B taken, trying to lock A
while the input_decoder client would have locked A already and would try
to flush the decoder implementation, taking lock B at the same time.

In practice, lock A is not really "useful" given that most of the
decoder_t methods are called from the decoder thread (owned by the input
decoder implementation), lock B is hidden in this part of the code, and
most of the "input decoder" is protected by the fifo lock, which then
must not be taken when calling decoder_t function to avoid reentrance of
the lock.

Co-authored-by: Thomas Guillem <thomas at gllm.f>

- - - - -
34a548cc by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
input: decoder: ensure decoder can be flushed

Flush was mostly entirely executed from the decoder thread, which meant
that it was blocked by previous call from decoder_t::pf_decode. Since
the decoder_t::pf_decode could be blocked waiting for a picture from the
vout to be able to resume decoding, and that flushing the video output
was done in this thread, it could deadlock.

A previous mechanism, picture_pool_Cancel, was introduced to allow the
external code to cancel the picture pool from the core and ensure
pf_decode would stop as soon as possible, but it was leading to more
spurrious unhandleable errors and could not work with decoder owning
their own pool.

Instead, ensure we set the flushing state directly from the flush call,
and flush the outputs. Setting the state will prevent decoder from
queueing new pictures by discarding them directly, which means that we
don't need to flush after flush has happened, and the flushing state
will be reset before new frames are queued into the decoder.

Fixes #26915

Co-authored-by: Thomas Guillem <thomas at gllm.f>

- - - - -
18e83e64 by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
input: decoder: move reset of i_preroll_end

The i_preroll_end state is reset at the end of the flush call on the
es_out side, but it's only set from the decoder thread which would be
flushing the decoders anyway, so it can be set from there (ie. the last
point of flush) where it's already locked.

- - - - -
91aabbf0 by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
test: input_decoder: check that flush works

This test provides a non-regression test for the issue #26915, in which
a decoder with internal pool gets stuck when flush is not done correctly
and which has been fixed right before.

- - - - -
29303ae4 by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
input: decoder: add stream output flush test

The test check that the stream output is correctly flushed when the
input decoder is flushed, ensuring that previous commit didn't break
the triggering of the flush.

- - - - -
f68a7291 by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
input: decoder: add documentation for the file

- - - - -
889548be by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
picture_pool: remove picture_pool_Cancel

There is no usage for picture_pool_Cancel anymore. The pool cancellation
was a mechanism for asynchronous signalling within decoders that the
decoder was being flushed and that waiting on picture could be
cancelled. Since it was a concern for decoders, it was moved to the
decoder handling code by ensuring the decoder has enough picture to
finish decoding and reach the pf_flush function.

- - - - -
48bdd6cd by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
picture_pool: simplify picture_pool_Get

The while loop will return at the first loop, or not even reach the
inner of the loop if no picture is available, so transform the loop
into an early return and de-indent the picture pool cloning case.

- - - - -
b510622c by Alexandre Janniaux at 2022-09-12T06:13:46+00:00
input: decoder: use vout from cfg

p_owner->p_vout is supposed to be used under lock, but we already got an
holding reference of the vout from cfg.vout, so use it instead.

- - - - -


9 changed files:

- include/vlc_frame.h
- include/vlc_picture_pool.h
- src/input/decoder.c
- src/misc/fifo.c
- src/misc/picture_pool.c
- test/Makefile.am
- + test/src/input/decoder/input_decoder.c
- + test/src/input/decoder/input_decoder.h
- + test/src/input/decoder/input_decoder_scenarios.c


Changes:

=====================================
include/vlc_frame.h
=====================================
@@ -743,6 +743,28 @@ VLC_API size_t vlc_fifo_GetCount(const vlc_fifo_t *) VLC_USED;
  */
 VLC_API size_t vlc_fifo_GetBytes(const vlc_fifo_t *) VLC_USED;
 
+/**
+ * Checks whether the vlc_fifo_t object is being locked.
+ *
+ * This function checks if the calling thread holds a given vlc_fifo_t
+ * object. It has no side effects and is essentially intended for run-time
+ * debugging.
+ *
+ * @note This function is the vlc_fifo_t equivalent of vlc_mutex_held.
+ *
+ * @note To assert that the calling thread holds a lock, the helper macro
+ * vlc_fifo_Assert() should be used instead of this function.
+ *
+ * @retval false the fifo is not locked by the calling thread
+ * @retval true the fifo is locked by the calling thread
+ */
+VLC_API bool vlc_fifo_Held(const vlc_fifo_t *fifo) VLC_USED;
+
+/**
+ * Asserts that a vlc_fifo_t is locked by the calling thread.
+ */
+#define vlc_fifo_Assert(fifo) assert(vlc_fifo_Held(fifo))
+
 VLC_USED static inline bool vlc_fifo_IsEmpty(const vlc_fifo_t *fifo)
 {
     return vlc_queue_IsEmpty(vlc_fifo_queue(fifo));


=====================================
include/vlc_picture_pool.h
=====================================
@@ -104,14 +104,6 @@ VLC_API picture_t * picture_pool_Get( picture_pool_t * ) VLC_USED;
  */
 VLC_API picture_t *picture_pool_Wait(picture_pool_t *) VLC_USED;
 
-/**
- * Cancel the picture pool.
- *
- * It won't return any pictures via picture_pool_Get or picture_pool_Wait if
- * canceled is true. This function will also unblock picture_pool_Wait.
- */
-void picture_pool_Cancel( picture_pool_t *, bool canceled );
-
 /**
  * Reserves pictures from a pool and creates a new pool with those.
  *


=====================================
src/input/decoder.c
=====================================
@@ -55,6 +55,53 @@
 
 #include "../video_output/vout_internal.h"
 
+
+/**
+ * \file src/input/decoder.c
+ *
+ * The input decoder connects the input client pushing data to the
+ * decoder implementation (through the matching elementary stream)
+ * and the following output for audio, video and subtitles.
+ *
+ * It follows the locking rules below:
+ *
+ *  - The fifo cannot be locked when calling function from the
+ *    decoder module implementation.
+ *
+ *  - However, the decoder module implementation might indirectly
+ *    lock the fifo when calling the owner methods, in particular
+ *    to send a frame or update the output status.
+ *
+ *  - The input code can lock the fifo to modify the global state
+ *    of the input decoder.
+ *
+ * Backpressure preventing starvation is done by the pacing of the
+ * decoder, the calls into the decoder implementation, and the
+ * limits of the fifo queue.
+ *
+ * Basically a very fast decoder will often wait since the fifo will be
+ * consumed really quickly and thus almost never stay under the lock.
+ * Likewise, when the decoder is slower and the fifo can grow, it also
+ * means that the decoder thread will wait more often on the
+ * `decoder_t::pf_decode` call, which is done without the fifo lock as
+ * per above rules.
+ *
+ * In addition with the standard input/output cycle from the decoder,
+ * the video decoders can create sub-decoders for the closed captions
+ * support embedded in the supplementary information from the codecs.
+ *
+ * To do so, they need to create a `decoder_cc_desc_t` matching with the
+ * format that needs to be described (number of channels, type of
+ * channels) and they then create them along with the closed-captions
+ * content with `decoder_QueueCc`.
+ *
+ * In the `input/decoder.c` code, the access to the sub-decoders in the
+ * cc.pp_decoders table is protected through the `cc.lock` mutex.
+ * Taking this lock ensures that the sub-decoder won't get
+ * asynchronously removed while using it, and any mutex from the
+ * sub-decoder can then be taken under this lock.
+ **/
+
 /*
  * Possibles values set in p_owner->reload atomic
  */
@@ -100,7 +147,6 @@ struct vlc_input_decoder_t
     block_fifo_t *p_fifo;
 
     /* Lock for communication with decoder thread */
-    vlc_mutex_t lock;
     vlc_cond_t  wait_request;
     vlc_cond_t  wait_acknowledge;
     vlc_cond_t  wait_fifo; /* TODO: merge with wait_acknowledge */
@@ -169,6 +215,7 @@ struct vlc_input_decoder_t
 #define MAX_CC_DECODERS 64 /* The es_out only creates one type of es */
     struct
     {
+        vlc_mutex_t lock;
         bool b_supported;
         decoder_cc_desc_t desc;
         vlc_input_decoder_t *pp_decoder[MAX_CC_DECODERS];
@@ -292,7 +339,7 @@ static void DecoderUpdateFormatLocked( vlc_input_decoder_t *p_owner )
 {
     decoder_t *p_dec = &p_owner->dec;
 
-    vlc_mutex_assert( &p_owner->lock );
+    vlc_fifo_Assert(p_owner->p_fifo);
 
     es_format_Clean( &p_owner->fmt );
     es_format_Copy( &p_owner->fmt, &p_dec->fmt_out );
@@ -352,10 +399,10 @@ static int ModuleThread_UpdateAudioFormat( decoder_t *p_dec )
         vlc_aout_stream *p_astream = p_owner->p_astream;
 
         /* Parameters changed, restart the aout */
-        vlc_mutex_lock( &p_owner->lock );
+        vlc_fifo_Lock(p_owner->p_fifo);
         p_owner->p_astream = NULL;
         p_owner->p_aout = NULL; // the DecoderThread should not use the old aout anymore
-        vlc_mutex_unlock( &p_owner->lock );
+        vlc_fifo_Unlock(p_owner->p_fifo);
         vlc_aout_stream_Delete( p_astream );
 
         input_resource_PutAout( p_owner->p_resource, p_aout );
@@ -413,23 +460,24 @@ static int ModuleThread_UpdateAudioFormat( decoder_t *p_dec )
         else
             p_astream = NULL;
 
-        vlc_mutex_lock( &p_owner->lock );
+        vlc_fifo_Lock(p_owner->p_fifo);
         p_owner->p_aout = p_aout;
         p_owner->p_astream = p_astream;
 
         DecoderUpdateFormatLocked( p_owner );
         aout_FormatPrepare( &p_owner->fmt.audio );
-        vlc_mutex_unlock( &p_owner->lock );
 
         if( p_aout == NULL )
+        {
+            vlc_fifo_Unlock(p_owner->p_fifo);
             return -1;
+        }
 
         p_dec->fmt_out.audio.i_bytes_per_frame =
             p_owner->fmt.audio.i_bytes_per_frame;
         p_dec->fmt_out.audio.i_frame_length =
             p_owner->fmt.audio.i_frame_length;
 
-        vlc_fifo_Lock( p_owner->p_fifo );
         p_owner->reset_out_state = true;
         vlc_fifo_Unlock( p_owner->p_fifo );
     }
@@ -459,7 +507,7 @@ static int ModuleThread_UpdateVideoFormat( decoder_t *p_dec, vlc_video_context *
     p_owner->vctx = vctx ? vlc_video_context_Hold(vctx) : NULL;
 
     // configure the new vout
-
+    vlc_fifo_Lock(p_owner->p_fifo);
     if ( p_owner->out_pool == NULL )
     {
         unsigned dpb_size;
@@ -492,13 +540,11 @@ static int ModuleThread_UpdateVideoFormat( decoder_t *p_dec, vlc_video_context *
             msg_Err(p_dec, "Failed to create a pool of %d %4.4s pictures",
                            dpb_size + p_dec->i_extra_picture_buffers + 1,
                            (char*)&p_dec->fmt_out.video.i_chroma);
+            vlc_fifo_Unlock(p_owner->p_fifo);
             goto error;
         }
 
-        vlc_mutex_lock( &p_owner->lock );
         p_owner->out_pool = pool;
-        vlc_mutex_unlock( &p_owner->lock );
-
     }
 
     vout_configuration_t cfg = {
@@ -507,6 +553,8 @@ static int ModuleThread_UpdateVideoFormat( decoder_t *p_dec, vlc_video_context *
         .fmt = &p_dec->fmt_out.video,
         .mouse_event = MouseEvent, .mouse_opaque = p_dec,
     };
+    vlc_fifo_Unlock(p_owner->p_fifo);
+
     enum input_resource_vout_state vout_state;
     vout_thread_t *p_vout =
         input_resource_RequestVout(p_owner->p_resource, vctx, &cfg, NULL,
@@ -516,18 +564,19 @@ static int ModuleThread_UpdateVideoFormat( decoder_t *p_dec, vlc_video_context *
         assert(vout_state == INPUT_RESOURCE_VOUT_NOTCHANGED ||
                vout_state == INPUT_RESOURCE_VOUT_STARTED);
 
-        vlc_mutex_lock( &p_owner->lock );
+        vlc_fifo_Lock(p_owner->p_fifo);
         p_owner->vout_started = true;
-        vlc_mutex_unlock( &p_owner->lock );
 
         if (vout_state == INPUT_RESOURCE_VOUT_STARTED)
         {
-            vlc_fifo_Lock( p_owner->p_fifo );
             p_owner->reset_out_state = true;
-            vlc_fifo_Unlock( p_owner->p_fifo );
+            vlc_fifo_Unlock(p_owner->p_fifo);
 
             decoder_Notify(p_owner, on_vout_started, p_vout, p_owner->vout_order);
         }
+        else
+            vlc_fifo_Unlock(p_owner->p_fifo);
+
         return 0;
     }
     else
@@ -536,15 +585,15 @@ static int ModuleThread_UpdateVideoFormat( decoder_t *p_dec, vlc_video_context *
                vout_state == INPUT_RESOURCE_VOUT_STOPPED);
 
         if (vout_state == INPUT_RESOURCE_VOUT_STOPPED)
-            decoder_Notify(p_owner, on_vout_stopped, p_owner->p_vout);
+            decoder_Notify(p_owner, on_vout_stopped, cfg.vout);
     }
 
 error:
     /* Clean fmt and vctx to trigger a new vout creation on the next update
      * call */
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock(p_owner->p_fifo);
     es_format_Clean( &p_owner->fmt );
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
 
     if (p_owner->vctx != NULL)
     {
@@ -559,6 +608,7 @@ static int CreateVoutIfNeeded(vlc_input_decoder_t *p_owner)
     decoder_t *p_dec = &p_owner->dec;
     bool need_vout = false;
 
+    vlc_fifo_Lock(p_owner->p_fifo);
     if( p_owner->p_vout == NULL )
     {
         msg_Dbg(p_dec, "vout: none found");
@@ -601,20 +651,21 @@ static int CreateVoutIfNeeded(vlc_input_decoder_t *p_owner)
     }
 
     if( !need_vout )
+    {
+        vlc_fifo_Unlock(p_owner->p_fifo);
         return 0; // vout unchanged
-
-    vlc_mutex_lock( &p_owner->lock );
+    }
 
     vout_thread_t *p_vout = p_owner->p_vout;
     p_owner->p_vout = NULL; // the DecoderThread should not use the old vout anymore
     p_owner->vout_started = false;
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock( p_owner->p_fifo );
 
     enum vlc_vout_order order;
     const vout_configuration_t cfg = { .vout = p_vout, .fmt = NULL };
     p_vout = input_resource_RequestVout( p_owner->p_resource, NULL, &cfg, &order, NULL );
 
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock( p_owner->p_fifo );
     p_owner->p_vout = p_vout;
     p_owner->vout_order = order;
 
@@ -622,7 +673,7 @@ static int CreateVoutIfNeeded(vlc_input_decoder_t *p_owner)
     p_owner->fmt.video.i_chroma = p_dec->fmt_out.i_codec;
     picture_pool_t *pool = p_owner->out_pool;
     p_owner->out_pool = NULL;
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock( p_owner->p_fifo );
 
      if ( pool != NULL )
          picture_pool_Release( pool );
@@ -673,9 +724,9 @@ static vlc_decoder_device * ModuleThread_GetDecoderDevice( decoder_t *p_dec )
     if ( need_format_update )
     {
         /* the format has changed but we don't need a new vout */
-        vlc_mutex_lock( &p_owner->lock );
+        vlc_fifo_Lock(p_owner->p_fifo);
         DecoderUpdateFormatLocked( p_owner );
-        vlc_mutex_unlock( &p_owner->lock );
+        vlc_fifo_Unlock(p_owner->p_fifo);
     }
     return dec_device;
 }
@@ -720,14 +771,14 @@ static subpicture_t *ModuleThread_NewSpuBuffer( decoder_t *p_dec,
             assert(p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID);
             decoder_Notify(p_owner, on_vout_stopped, p_owner->p_vout);
 
-            vlc_mutex_lock( &p_owner->lock );
+            vlc_fifo_Lock( p_owner->p_fifo );
             vout_UnregisterSubpictureChannel(p_owner->p_vout,
                                              p_owner->i_spu_channel);
             p_owner->i_spu_channel = VOUT_SPU_CHANNEL_INVALID;
 
             vout_Release(p_owner->p_vout);
             p_owner->p_vout = NULL; // the DecoderThread should not use the old vout anymore
-            vlc_mutex_unlock( &p_owner->lock );
+            vlc_fifo_Lock( p_owner->p_fifo );
         }
         return NULL;
     }
@@ -737,7 +788,7 @@ static subpicture_t *ModuleThread_NewSpuBuffer( decoder_t *p_dec,
         if (p_owner->p_vout) /* notify the previous vout deletion unlocked */
             decoder_Notify(p_owner, on_vout_stopped, p_owner->p_vout);
 
-        vlc_mutex_lock(&p_owner->lock);
+        vlc_fifo_Lock(p_owner->p_fifo);
 
         if (p_owner->p_vout)
         {
@@ -758,14 +809,14 @@ static subpicture_t *ModuleThread_NewSpuBuffer( decoder_t *p_dec,
         if (p_owner->i_spu_channel == VOUT_SPU_CHANNEL_INVALID)
         {
             /* The new vout doesn't support SPU, aborting... */
-            vlc_mutex_unlock(&p_owner->lock);
+            vlc_fifo_Unlock(p_owner->p_fifo);
             vout_Release(p_vout);
             return NULL;
         }
 
         p_owner->p_vout = p_vout;
         p_owner->vout_order = channel_order;
-        vlc_mutex_unlock(&p_owner->lock);
+        vlc_fifo_Unlock(p_owner->p_fifo);
 
         assert(channel_order != VLC_VOUT_ORDER_NONE);
         decoder_Notify(p_owner, on_vout_started, p_vout, channel_order);
@@ -805,11 +856,11 @@ static vlc_tick_t ModuleThread_GetDisplayDate( decoder_t *p_dec,
 {
     vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
 
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock(p_owner->p_fifo);
     if( p_owner->b_waiting || p_owner->paused )
         i_ts = VLC_TICK_INVALID;
     float rate = p_owner->output_rate;
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
 
     if( !p_owner->p_clock || i_ts == VLC_TICK_INVALID )
         return i_ts;
@@ -823,9 +874,9 @@ static float ModuleThread_GetDisplayRate( decoder_t *p_dec )
 
     if( !p_owner->p_clock )
         return 1.f;
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock(p_owner->p_fifo);
     float rate = p_owner->output_rate;
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
     return rate;
 }
 
@@ -857,7 +908,7 @@ static void RequestReload( vlc_input_decoder_t *p_owner )
 
 static void DecoderWaitUnblock( vlc_input_decoder_t *p_owner )
 {
-    vlc_mutex_assert( &p_owner->lock );
+    vlc_fifo_Assert(p_owner->p_fifo);
 
     if( p_owner->b_waiting )
     {
@@ -866,7 +917,7 @@ static void DecoderWaitUnblock( vlc_input_decoder_t *p_owner )
     }
 
     while( p_owner->b_waiting && p_owner->b_has_data )
-        vlc_cond_wait( &p_owner->wait_request, &p_owner->lock );
+        vlc_fifo_WaitCond(p_owner->p_fifo, &p_owner->wait_request);
 }
 
 static inline void DecoderUpdatePreroll( vlc_tick_t *pi_preroll, const vlc_frame_t *p )
@@ -884,6 +935,47 @@ static inline void DecoderUpdatePreroll( vlc_tick_t *pi_preroll, const vlc_frame
 }
 
 #ifdef ENABLE_SOUT
+
+static void DecoderSendSubstream(vlc_input_decoder_t *p_owner)
+{
+    decoder_t *p_dec = &p_owner->dec;
+    if (p_dec->pf_get_cc == NULL)
+        return;
+
+    bool b_wants_substreams;
+    int ret = sout_StreamControl(p_owner->p_sout,
+                                 SOUT_STREAM_WANTS_SUBSTREAMS,
+                                 &b_wants_substreams);
+
+    if (ret != VLC_SUCCESS || !b_wants_substreams)
+        return;
+
+    if (p_owner->cc.p_sout_input == NULL && p_owner->cc.b_sout_created)
+        return;
+
+    decoder_cc_desc_t desc;
+    vlc_frame_t *p_cc = p_dec->pf_get_cc( p_dec, &desc );
+    if (p_cc == NULL)
+        return;
+
+    if (!p_owner->cc.b_sout_created)
+    {
+        es_format_t ccfmt;
+        es_format_Init(&ccfmt, SPU_ES, VLC_CODEC_CEA608);
+        ccfmt.i_group = p_owner->fmt.i_group;
+        ccfmt.subs.cc.i_reorder_depth = desc.i_reorder_depth;
+        p_owner->cc.p_sout_input = sout_InputNew( p_owner->p_sout, &ccfmt );
+        es_format_Clean(&ccfmt);
+        p_owner->cc.b_sout_created = true;
+    }
+
+    if (!p_owner->cc.p_sout_input ||
+        sout_InputSendBuffer(p_owner->p_sout, p_owner->cc.p_sout_input, p_cc))
+    {
+        block_Release(p_cc);
+    }
+}
+
 /* This function process a frame for sout
  */
 static void DecoderThread_ProcessSout( vlc_input_decoder_t *p_owner, vlc_frame_t *frame )
@@ -897,7 +989,7 @@ static void DecoderThread_ProcessSout( vlc_input_decoder_t *p_owner, vlc_frame_t
     {
         if( p_owner->p_sout_input == NULL )
         {
-            vlc_mutex_lock( &p_owner->lock );
+            vlc_fifo_Lock(p_owner->p_fifo);
             DecoderUpdateFormatLocked( p_owner );
 
             p_owner->fmt.i_group = p_dec->fmt_in.i_group;
@@ -908,7 +1000,7 @@ static void DecoderThread_ProcessSout( vlc_input_decoder_t *p_owner, vlc_frame_t
                 p_owner->fmt.psz_language =
                     strdup( p_dec->fmt_in.psz_language );
             }
-            vlc_mutex_unlock( &p_owner->lock );
+            vlc_fifo_Unlock(p_owner->p_fifo);
 
             p_owner->p_sout_input =
                 sout_InputNew( p_owner->p_sout, &p_owner->fmt );
@@ -930,42 +1022,10 @@ static void DecoderThread_ProcessSout( vlc_input_decoder_t *p_owner, vlc_frame_t
         while( sout_frame )
         {
             vlc_frame_t *p_next = sout_frame->p_next;
-            bool b_wants_substreams;
 
             sout_frame->p_next = NULL;
 
-            if( p_dec->pf_get_cc
-             && sout_StreamControl( p_owner->p_sout,
-                                    SOUT_STREAM_WANTS_SUBSTREAMS,
-                                    &b_wants_substreams ) == VLC_SUCCESS
-             && b_wants_substreams )
-            {
-                if( p_owner->cc.p_sout_input ||
-                    !p_owner->cc.b_sout_created )
-                {
-                    decoder_cc_desc_t desc;
-                    vlc_frame_t *p_cc = p_dec->pf_get_cc( p_dec, &desc );
-                    if( p_cc )
-                    {
-                        if(!p_owner->cc.b_sout_created)
-                        {
-                            es_format_t ccfmt;
-                            es_format_Init(&ccfmt, SPU_ES, VLC_CODEC_CEA608);
-                            ccfmt.i_group = p_owner->fmt.i_group;
-                            ccfmt.subs.cc.i_reorder_depth = desc.i_reorder_depth;
-                            p_owner->cc.p_sout_input = sout_InputNew( p_owner->p_sout, &ccfmt );
-                            es_format_Clean(&ccfmt);
-                            p_owner->cc.b_sout_created = true;
-                        }
-
-                        if( !p_owner->cc.p_sout_input ||
-                            sout_InputSendBuffer( p_owner->p_sout, p_owner->cc.p_sout_input, p_cc ) )
-                        {
-                            block_Release( p_cc );
-                        }
-                    }
-                }
-            }
+            DecoderSendSubstream( p_owner );
 
             /* FIXME --VLC_TICK_INVALID inspect stream_output*/
             if ( sout_InputSendBuffer( p_owner->p_sout, p_owner->p_sout_input, sout_frame ) ==
@@ -994,8 +1054,15 @@ static void DecoderThread_ProcessSout( vlc_input_decoder_t *p_owner, vlc_frame_t
 static void DecoderPlayCc( vlc_input_decoder_t *p_owner, vlc_frame_t *p_cc,
                            const decoder_cc_desc_t *p_desc )
 {
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock(p_owner->p_fifo);
+    if (p_owner->flushing)
+    {
+        vlc_fifo_Unlock(p_owner->p_fifo);
+        vlc_frame_Release(p_cc);
+        return;
+    }
 
+    vlc_mutex_lock(&p_owner->cc.lock);
     p_owner->cc.desc = *p_desc;
 
     /* Fanout data to all decoders. We do not know if es_out
@@ -1019,8 +1086,9 @@ static void DecoderPlayCc( vlc_input_decoder_t *p_owner, vlc_frame_t *p_cc,
             p_cc = NULL; /* was last dec */
         }
     }
+    vlc_mutex_unlock(&p_owner->cc.lock);
 
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
 
     if( p_cc ) /* can have bitmap set but no created decs */
         block_Release( p_cc );
@@ -1061,7 +1129,6 @@ static void ModuleThread_QueueCc( decoder_t *p_videodec, vlc_frame_t *p_cc,
 static int ModuleThread_PlayVideo( vlc_input_decoder_t *p_owner, picture_t *p_picture )
 {
     decoder_t *p_dec = &p_owner->dec;
-    vout_thread_t  *p_vout = p_owner->p_vout;
 
     if( p_picture->date == VLC_TICK_INVALID )
         /* FIXME: VLC_TICK_INVALID -- verify video_output */
@@ -1071,13 +1138,22 @@ static int ModuleThread_PlayVideo( vlc_input_decoder_t *p_owner, picture_t *p_pi
         return VLC_EGENERIC;
     }
 
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock( p_owner->p_fifo );
+    if (p_owner->flushing)
+    {
+        vlc_fifo_Unlock(p_owner->p_fifo);
+        picture_Release(p_picture);
+        return VLC_SUCCESS;
+    }
+
+    vout_thread_t  *p_vout = p_owner->p_vout;
+
     assert( p_owner->vout_started );
 
     bool prerolled = p_owner->i_preroll_end != PREROLL_NONE;
     if( prerolled && p_owner->i_preroll_end > p_picture->date )
     {
-        vlc_mutex_unlock( &p_owner->lock );
+        vlc_fifo_Unlock( p_owner->p_fifo );
         picture_Release( p_picture );
         return VLC_SUCCESS;
     }
@@ -1099,21 +1175,18 @@ static int ModuleThread_PlayVideo( vlc_input_decoder_t *p_owner, picture_t *p_pi
         p_picture->b_force = true;
     }
     else
+    {
         DecoderWaitUnblock( p_owner );
+    }
 
-    vlc_mutex_unlock( &p_owner->lock );
-
-    /* FIXME: The *input* FIFO should not be locked here. This will not work
-     * properly if/when pictures are queued asynchronously. */
-    vlc_fifo_Lock( p_owner->p_fifo );
     if( unlikely(p_owner->paused) && likely(p_owner->frames_countdown > 0) )
         p_owner->frames_countdown--;
-    vlc_fifo_Unlock( p_owner->p_fifo );
 
     /* */
     if( p_vout == NULL )
     {
         picture_Release( p_picture );
+        vlc_fifo_Unlock(p_owner->p_fifo);
         return VLC_EGENERIC;
     }
 
@@ -1123,6 +1196,7 @@ static int ModuleThread_PlayVideo( vlc_input_decoder_t *p_owner, picture_t *p_pi
         vout_Flush( p_vout, p_picture->date );
     }
     vout_PutPicture( p_vout, p_picture );
+    vlc_fifo_Unlock(p_owner->p_fifo);
 
     return VLC_SUCCESS;
 }
@@ -1172,13 +1246,13 @@ static picture_t *thumbnailer_buffer_new( decoder_t *p_dec )
     vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
     /* Avoid decoding more than one frame when a thumbnail was
      * already generated */
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock(p_owner->p_fifo);
     if( !p_owner->b_first )
     {
-        vlc_mutex_unlock( &p_owner->lock );
+        vlc_fifo_Unlock(p_owner->p_fifo);
         return NULL;
     }
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
     return picture_NewFromFormat( &p_dec->fmt_out.video );
 }
 
@@ -1187,10 +1261,10 @@ static void ModuleThread_QueueThumbnail( decoder_t *p_dec, picture_t *p_pic )
     vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
     bool b_first;
 
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock(p_owner->p_fifo);
     b_first = p_owner->b_first;
     p_owner->b_first = false;
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
 
     if( b_first )
         decoder_Notify(p_owner, on_thumbnail_ready, p_pic);
@@ -1211,17 +1285,24 @@ static int ModuleThread_PlayAudio( vlc_input_decoder_t *p_owner, vlc_frame_t *p_
         return VLC_EGENERIC;
     }
 
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock(p_owner->p_fifo);
+    if (p_owner->flushing)
+    {
+        vlc_fifo_Unlock(p_owner->p_fifo);
+        block_Release(p_audio);
+        return VLC_SUCCESS;
+    }
+
     bool prerolled = p_owner->i_preroll_end != PREROLL_NONE;
     if( prerolled && p_owner->i_preroll_end > p_audio->i_pts )
     {
-        vlc_mutex_unlock( &p_owner->lock );
+        vlc_fifo_Unlock(p_owner->p_fifo);
         block_Release( p_audio );
         return VLC_SUCCESS;
     }
 
     p_owner->i_preroll_end = PREROLL_NONE;
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
 
     if( unlikely(prerolled) )
     {
@@ -1233,11 +1314,11 @@ static int ModuleThread_PlayAudio( vlc_input_decoder_t *p_owner, vlc_frame_t *p_
 
     /* */
     /* */
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock(p_owner->p_fifo);
 
     /* */
     DecoderWaitUnblock( p_owner );
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
 
     vlc_aout_stream *p_astream = p_owner->p_astream;
 
@@ -1307,10 +1388,9 @@ static void ModuleThread_PlaySpu( vlc_input_decoder_t *p_owner, subpicture_t *p_
     }
 
     /* */
-    vlc_mutex_lock( &p_owner->lock );
-
+    vlc_fifo_Lock(p_owner->p_fifo);
     DecoderWaitUnblock( p_owner );
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
 
     if( p_subpic->i_start == VLC_TICK_INVALID )
     {
@@ -1337,17 +1417,17 @@ static void ModuleThread_QueueSpu( decoder_t *p_dec, subpicture_t *p_spu )
     assert( p_owner->p_vout );
 
     /* Preroll does not work very well with subtitle */
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock(p_owner->p_fifo);
     if( p_spu->i_start != VLC_TICK_INVALID &&
         p_spu->i_start < p_owner->i_preroll_end &&
         ( p_spu->i_stop == VLC_TICK_INVALID || p_spu->i_stop < p_owner->i_preroll_end ) )
     {
-        vlc_mutex_unlock( &p_owner->lock );
+        vlc_fifo_Lock(p_owner->p_fifo);
         subpicture_Delete( p_spu );
     }
     else
     {
-        vlc_mutex_unlock( &p_owner->lock );
+        vlc_fifo_Unlock(p_owner->p_fifo);
         ModuleThread_PlaySpu( p_owner, p_spu );
     }
 }
@@ -1422,9 +1502,9 @@ static void DecoderThread_ProcessInput( vlc_input_decoder_t *p_owner, vlc_frame_
         if( frame->i_buffer <= 0 )
             goto error;
 
-        vlc_mutex_lock( &p_owner->lock );
+        vlc_fifo_Lock(p_owner->p_fifo);
         DecoderUpdatePreroll( &p_owner->i_preroll_end, frame );
-        vlc_mutex_unlock( &p_owner->lock );
+        vlc_fifo_Unlock(p_owner->p_fifo);
         if( unlikely( frame->i_flags & BLOCK_FLAG_CORE_PRIVATE_RELOADED ) )
         {
             /* This frame has already been packetized */
@@ -1509,69 +1589,37 @@ static void DecoderThread_Flush( vlc_input_decoder_t *p_owner )
         p_dec->pf_flush( p_dec );
 
     /* flush CC sub decoders */
+    vlc_mutex_lock(&p_owner->cc.lock);
     if( p_owner->cc.b_supported )
     {
         for( int i=0; i<MAX_CC_DECODERS; i++ )
         {
             vlc_input_decoder_t *p_ccowner = p_owner->cc.pp_decoder[i];
-            if( p_ccowner && p_ccowner->dec.pf_flush )
-                p_ccowner->dec.pf_flush( &p_ccowner->dec );
-        }
-    }
-
-    vlc_mutex_lock( &p_owner->lock );
-#ifdef ENABLE_SOUT
-    if ( p_owner->p_sout_input != NULL )
-    {
-        sout_InputFlush( p_owner->p_sout, p_owner->p_sout_input );
-    }
-#endif
-    if( p_dec->fmt_in.i_cat == AUDIO_ES )
-    {
-        if( p_owner->p_astream )
-            vlc_aout_stream_Flush( p_owner->p_astream );
-    }
-    else if( p_dec->fmt_in.i_cat == VIDEO_ES )
-    {
-        if( p_owner->p_vout && p_owner->vout_started )
-            vout_FlushAll( p_owner->p_vout );
+            if(p_ccowner == NULL)
+                continue;
 
-        /* Reset the pool cancel state, previously set by
-         * vlc_input_decoder_Flush() */
-        if( p_owner->out_pool != NULL )
-            picture_pool_Cancel( p_owner->out_pool, false );
-    }
-    else if( p_dec->fmt_in.i_cat == SPU_ES )
-    {
-        if( p_owner->p_vout )
-        {
-            assert( p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID );
-            vout_FlushSubpictureChannel( p_owner->p_vout, p_owner->i_spu_channel );
+            vlc_input_decoder_Flush(p_ccowner);
         }
     }
-
-    p_owner->i_preroll_end = PREROLL_NONE;
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_mutex_unlock(&p_owner->cc.lock);
 }
 
 static void DecoderThread_ChangePause( vlc_input_decoder_t *p_owner, bool paused, vlc_tick_t date )
 {
+    vlc_fifo_Assert(p_owner->p_fifo);
+
     decoder_t *p_dec = &p_owner->dec;
 
     msg_Dbg( p_dec, "toggling %s", paused ? "resume" : "pause" );
     switch( p_dec->fmt_in.i_cat )
     {
         case VIDEO_ES:
-            vlc_mutex_lock( &p_owner->lock );
             if( p_owner->p_vout != NULL && p_owner->vout_started )
                 vout_ChangePause( p_owner->p_vout, paused, date );
-            vlc_mutex_unlock( &p_owner->lock );
             break;
         case AUDIO_ES:
-            vlc_mutex_lock( &p_owner->lock );
             if( p_owner->p_astream != NULL )
                 vlc_aout_stream_ChangePause( p_owner->p_astream, paused, date );
-            vlc_mutex_unlock( &p_owner->lock );
             break;
         case SPU_ES:
             break;
@@ -1582,10 +1630,11 @@ static void DecoderThread_ChangePause( vlc_input_decoder_t *p_owner, bool paused
 
 static void DecoderThread_ChangeRate( vlc_input_decoder_t *p_owner, float rate )
 {
+    vlc_fifo_Assert(p_owner->p_fifo);
+
     decoder_t *p_dec = &p_owner->dec;
 
     msg_Dbg( p_dec, "changing rate: %f", rate );
-    vlc_mutex_lock( &p_owner->lock );
     switch( p_dec->fmt_in.i_cat )
     {
         case VIDEO_ES:
@@ -1608,38 +1657,32 @@ static void DecoderThread_ChangeRate( vlc_input_decoder_t *p_owner, float rate )
             vlc_assert_unreachable();
     }
     p_owner->output_rate = rate;
-    vlc_mutex_unlock( &p_owner->lock );
 }
 
 static void DecoderThread_ChangeDelay( vlc_input_decoder_t *p_owner, vlc_tick_t delay )
 {
+    vlc_fifo_Assert(p_owner->p_fifo);
+
     decoder_t *p_dec = &p_owner->dec;
 
     msg_Dbg( p_dec, "changing delay: %"PRId64, delay );
-
     switch( p_dec->fmt_in.i_cat )
     {
         case VIDEO_ES:
-            vlc_mutex_lock( &p_owner->lock );
             if( p_owner->p_vout != NULL && p_owner->vout_started )
                 vout_ChangeDelay( p_owner->p_vout, delay );
-            vlc_mutex_unlock( &p_owner->lock );
             break;
         case AUDIO_ES:
-            vlc_mutex_lock( &p_owner->lock );
             if( p_owner->p_astream != NULL )
                 vlc_aout_stream_ChangeDelay( p_owner->p_astream, delay );
-            vlc_mutex_unlock( &p_owner->lock );
             break;
         case SPU_ES:
-            vlc_mutex_lock( &p_owner->lock );
             if( p_owner->p_vout != NULL )
             {
                 assert(p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID);
                 vout_ChangeSpuDelay(p_owner->p_vout, p_owner->i_spu_channel,
                                     delay);
             }
-            vlc_mutex_unlock( &p_owner->lock );
             break;
         default:
             vlc_assert_unreachable();
@@ -1689,7 +1732,7 @@ static void *DecoderThread( void *p_data )
              * is called again. This will avoid a second useless flush (but
              * harmless). */
             p_owner->flushing = false;
-
+            p_owner->i_preroll_end = PREROLL_NONE;
             continue;
         }
 
@@ -1709,33 +1752,21 @@ static void *DecoderThread( void *p_data )
             vlc_tick_t date = p_owner->pause_date;
 
             paused = p_owner->paused;
-            vlc_fifo_Unlock( p_owner->p_fifo );
-
             DecoderThread_ChangePause( p_owner, paused, date );
-
-            vlc_fifo_Lock( p_owner->p_fifo );
             continue;
         }
 
         if( rate != p_owner->request_rate )
         {
             rate = p_owner->request_rate;
-            vlc_fifo_Unlock( p_owner->p_fifo );
-
             DecoderThread_ChangeRate( p_owner, rate );
-
-            vlc_fifo_Lock( p_owner->p_fifo );
             continue;
         }
 
         if( delay != p_owner->delay )
         {
             delay = p_owner->delay;
-            vlc_fifo_Unlock( p_owner->p_fifo );
-
             DecoderThread_ChangeDelay( p_owner, delay );
-
-            vlc_fifo_Lock( p_owner->p_fifo );
             continue;
         }
 
@@ -1777,12 +1808,10 @@ static void *DecoderThread( void *p_data )
         }
 
         /* TODO? Wait for draining instead of polling. */
-        vlc_mutex_lock( &p_owner->lock );
-        vlc_fifo_Lock( p_owner->p_fifo );
+        vlc_fifo_Lock(p_owner->p_fifo);
         if( p_owner->b_draining && (frame == NULL) )
             p_owner->b_draining = false;
         vlc_cond_signal( &p_owner->wait_acknowledge );
-        vlc_mutex_unlock( &p_owner->lock );
     }
 
     vlc_fifo_Unlock( p_owner->p_fifo );
@@ -1903,7 +1932,6 @@ CreateDecoder( vlc_object_t *p_parent, const struct vlc_input_decoder_cfg *cfg )
         return NULL;
     }
 
-    vlc_mutex_init( &p_owner->lock );
     vlc_mutex_init( &p_owner->mouse_lock );
     vlc_cond_init( &p_owner->wait_request );
     vlc_cond_init( &p_owner->wait_acknowledge );
@@ -1973,6 +2001,7 @@ CreateDecoder( vlc_object_t *p_parent, const struct vlc_input_decoder_cfg *cfg )
     }
 
     /* */
+    vlc_mutex_init(&p_owner->cc.lock);
     p_owner->cc.b_supported = ( cfg->sout == NULL );
 
     p_owner->cc.desc.i_608_channels = 0;
@@ -2201,12 +2230,10 @@ void vlc_input_decoder_Delete( vlc_input_decoder_t *p_owner )
     vlc_fifo_Lock( p_owner->p_fifo );
     p_owner->aborting = true;
     p_owner->flushing = true;
+    p_owner->b_waiting = false;
     vlc_fifo_Signal( p_owner->p_fifo );
-    vlc_fifo_Unlock( p_owner->p_fifo );
 
     /* Make sure we aren't waiting/decoding anymore */
-    vlc_mutex_lock( &p_owner->lock );
-    p_owner->b_waiting = false;
     vlc_cond_signal( &p_owner->wait_request );
 
     /* If the video output is paused or slow, or if the picture pool size was
@@ -2219,9 +2246,6 @@ void vlc_input_decoder_Delete( vlc_input_decoder_t *p_owner )
     if( p_dec->fmt_in.i_cat == VIDEO_ES && p_owner->p_vout != NULL
      && p_owner->vout_started )
     {
-        if (p_owner->out_pool)
-            picture_pool_Cancel( p_owner->out_pool, true );
-
         if( p_owner->paused )
         {
             /* The DecoderThread could be stuck in pf_decode(). This is likely the
@@ -2238,7 +2262,7 @@ void vlc_input_decoder_Delete( vlc_input_decoder_t *p_owner )
             vout_FlushAll( p_owner->p_vout );
         }
     }
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock( p_owner->p_fifo );
 
     if( !vlc_input_decoder_IsSynchronous( p_owner ) )
         vlc_join( p_owner->thread, NULL );
@@ -2309,11 +2333,9 @@ bool vlc_input_decoder_IsEmpty( vlc_input_decoder_t * p_owner )
         vlc_fifo_Unlock( p_owner->p_fifo );
         return false;
     }
-    vlc_fifo_Unlock( p_owner->p_fifo );
 
     bool b_empty;
 
-    vlc_mutex_lock( &p_owner->lock );
 #ifdef ENABLE_SOUT
     if( p_owner->p_sout_input != NULL )
         b_empty = true;
@@ -2325,7 +2347,7 @@ bool vlc_input_decoder_IsEmpty( vlc_input_decoder_t * p_owner )
         b_empty = vlc_aout_stream_IsDrained( p_owner->p_astream );
     else
         b_empty = true; /* TODO subtitles support */
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock( p_owner->p_fifo );
 
     return b_empty;
 }
@@ -2359,12 +2381,6 @@ void vlc_input_decoder_Drain( vlc_input_decoder_t *p_owner )
  */
 void vlc_input_decoder_Flush( vlc_input_decoder_t *p_owner )
 {
-    if( vlc_input_decoder_IsSynchronous( p_owner ) )
-    {
-        DecoderThread_Flush( p_owner );
-        return;
-    }
-
     enum es_format_category_e cat = p_owner->dec.fmt_in.i_cat;
 
     vlc_fifo_Lock( p_owner->p_fifo );
@@ -2384,38 +2400,43 @@ void vlc_input_decoder_Flush( vlc_input_decoder_t *p_owner )
      && p_owner->frames_countdown == 0 )
         p_owner->frames_countdown++;
 
-    vlc_fifo_Signal( p_owner->p_fifo );
-
-    vlc_fifo_Unlock( p_owner->p_fifo );
-
-    if ( cat == VIDEO_ES )
+    if ( p_owner->p_sout_input != NULL )
     {
-        /* Set the pool cancel state. This will unblock the module if it is
-         * waiting for new pictures (likely). This state will be reset back
-         * from the DecoderThread once the flush request is processed. */
-        vlc_mutex_lock( &p_owner->lock );
-        if( p_owner->out_pool != NULL )
-            picture_pool_Cancel( p_owner->out_pool, true );
-        vlc_mutex_unlock( &p_owner->lock );
+#ifdef ENABLE_SOUT
+        sout_InputFlush( p_owner->p_sout, p_owner->p_sout_input );
+#endif
     }
-
-    if( p_owner->paused )
+    else if( cat == AUDIO_ES )
     {
-        /* The DecoderThread could be stuck in pf_decode(). This is likely the
-         * case with paused asynchronous decoder modules that have a limited
-         * input and output pool size. Indeed, with such decoders, you have to
-         * release an output buffer to get an input buffer. So, when paused and
-         * flushed, the DecoderThread could be waiting for an output buffer to
-         * be released (or rendered). In that case, the DecoderThread will
-         * never be flushed since it be never leave pf_decode(). To fix this
-         * issue, pre-flush the vout from here. The vout will have to be
-         * flushed again since the module could be outputting more buffers just
-         * after being unstuck. */
-
-        vlc_mutex_lock( &p_owner->lock );
-        if( cat == VIDEO_ES && p_owner->p_vout && p_owner->vout_started )
+        if( p_owner->p_astream )
+            vlc_aout_stream_Flush( p_owner->p_astream );
+    }
+    else if( cat == VIDEO_ES )
+    {
+        if( p_owner->p_vout && p_owner->vout_started )
             vout_FlushAll( p_owner->p_vout );
-        vlc_mutex_unlock( &p_owner->lock );
+    }
+    else if( cat == SPU_ES )
+    {
+        if( p_owner->p_vout )
+        {
+            assert( p_owner->i_spu_channel != VOUT_SPU_CHANNEL_INVALID );
+            vout_FlushSubpictureChannel( p_owner->p_vout, p_owner->i_spu_channel );
+        }
+    }
+    vlc_fifo_Signal( p_owner->p_fifo );
+    vlc_fifo_Unlock( p_owner->p_fifo );
+
+    if (vlc_input_decoder_IsSynchronous(p_owner))
+    {
+        /* With a synchronous decoder,there is no decoder thread which
+         * can process the flush request. We flush synchronously from
+         * here and reset the flushing state. */
+        DecoderThread_Flush(p_owner);
+        vlc_fifo_Lock(p_owner->p_fifo);
+        p_owner->flushing = false;
+        p_owner->i_preroll_end = PREROLL_NONE;
+        vlc_fifo_Unlock(p_owner->p_fifo);
     }
 }
 
@@ -2446,8 +2467,12 @@ int vlc_input_decoder_SetCcState( vlc_input_decoder_t *p_owner, vlc_fourcc_t cod
     decoder_t *p_dec = &p_owner->dec;
     //msg_Warn( p_dec, "vlc_input_decoder_SetCcState: %d @%x", b_decode, i_channel );
 
+    vlc_mutex_lock(&p_owner->cc.lock);
     if( !vlc_input_decoder_HasCCChanFlag( p_owner, codec, i_channel ) )
+    {
+        vlc_mutex_unlock(&p_owner->cc.lock);
         return VLC_EGENERIC;
+    }
 
     if( b_decode )
     {
@@ -2475,44 +2500,46 @@ int vlc_input_decoder_SetCcState( vlc_input_decoder_t *p_owner, vlc_fourcc_t cod
             vlc_dialog_display_error( p_dec,
                 _("Streaming / Transcoding failed"), "%s",
                 _("VLC could not open the decoder module.") );
+            vlc_mutex_unlock(&p_owner->cc.lock);
             return VLC_EGENERIC;
         }
         else if( !p_ccowner->dec.p_module )
         {
             DecoderUnsupportedCodec( p_dec, &fmt, true );
             vlc_input_decoder_Delete(p_ccowner);
+            vlc_mutex_unlock(&p_owner->cc.lock);
             return VLC_EGENERIC;
         }
         p_ccowner->p_clock = p_owner->p_clock;
 
-        vlc_mutex_lock( &p_owner->lock );
         p_owner->cc.pp_decoder[i_channel] = p_ccowner;
-        vlc_mutex_unlock( &p_owner->lock );
     }
     else
     {
         vlc_input_decoder_t *p_cc;
 
-        vlc_mutex_lock( &p_owner->lock );
         p_cc = p_owner->cc.pp_decoder[i_channel];
         p_owner->cc.pp_decoder[i_channel] = NULL;
-        vlc_mutex_unlock( &p_owner->lock );
 
         if( p_cc )
             vlc_input_decoder_Delete(p_cc);
     }
+    vlc_mutex_unlock(&p_owner->cc.lock);
     return VLC_SUCCESS;
 }
 
 int vlc_input_decoder_GetCcState( vlc_input_decoder_t *p_owner, vlc_fourcc_t codec,
                                   int i_channel, bool *pb_decode )
 {
+    vlc_mutex_lock(&p_owner->cc.lock);
     if( !vlc_input_decoder_HasCCChanFlag( p_owner, codec, i_channel ) )
+    {
+        vlc_mutex_unlock(&p_owner->cc.lock);
         return VLC_EGENERIC;
+    }
 
-    vlc_mutex_lock( &p_owner->lock );
     *pb_decode = p_owner->cc.pp_decoder[i_channel] != NULL;
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_mutex_unlock(&p_owner->cc.lock);
     return VLC_SUCCESS;
 }
 
@@ -2551,12 +2578,12 @@ void vlc_input_decoder_StartWait( vlc_input_decoder_t *p_owner )
 
     assert( !p_owner->b_waiting );
 
-    vlc_mutex_lock( &p_owner->lock );
-    p_owner->b_first = true;
+    vlc_fifo_Lock(p_owner->p_fifo);
     p_owner->b_has_data = false;
+    p_owner->b_first = true;
     p_owner->b_waiting = true;
-    vlc_cond_signal( &p_owner->wait_request );
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_cond_signal(&p_owner->wait_request);
+    vlc_fifo_Unlock(p_owner->p_fifo);
 }
 
 void vlc_input_decoder_StopWait( vlc_input_decoder_t *p_owner )
@@ -2564,12 +2591,11 @@ void vlc_input_decoder_StopWait( vlc_input_decoder_t *p_owner )
     if( vlc_input_decoder_IsSynchronous( p_owner ) )
         return;
 
+    vlc_fifo_Lock(p_owner->p_fifo);
     assert( p_owner->b_waiting );
-
-    vlc_mutex_lock( &p_owner->lock );
     p_owner->b_waiting = false;
     vlc_cond_signal( &p_owner->wait_request );
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
 }
 
 void vlc_input_decoder_Wait( vlc_input_decoder_t *p_owner )
@@ -2581,24 +2607,21 @@ void vlc_input_decoder_Wait( vlc_input_decoder_t *p_owner )
     }
     assert( p_owner->b_waiting );
 
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock(p_owner->p_fifo);
     while( !p_owner->b_has_data )
     {
         /* Don't need to lock p_owner->paused since it's only modified by the
          * owner */
         if( p_owner->paused )
             break;
-        vlc_fifo_Lock( p_owner->p_fifo );
         if( p_owner->b_idle && vlc_fifo_IsEmpty( p_owner->p_fifo ) )
         {
             msg_Err( &p_owner->dec, "buffer deadlock prevented" );
-            vlc_fifo_Unlock( p_owner->p_fifo );
             break;
         }
-        vlc_fifo_Unlock( p_owner->p_fifo );
-        vlc_cond_wait( &p_owner->wait_acknowledge, &p_owner->lock );
+        vlc_fifo_WaitCond(p_owner->p_fifo, &p_owner->wait_acknowledge);
     }
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
 }
 
 void vlc_input_decoder_FrameNext( vlc_input_decoder_t *p_owner,
@@ -2612,19 +2635,19 @@ void vlc_input_decoder_FrameNext( vlc_input_decoder_t *p_owner,
     vlc_fifo_Signal( p_owner->p_fifo );
     vlc_fifo_Unlock( p_owner->p_fifo );
 
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock(p_owner->p_fifo);
     if( p_owner->dec.fmt_in.i_cat == VIDEO_ES )
     {
         if( p_owner->p_vout )
             vout_NextPicture( p_owner->p_vout, pi_duration );
     }
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
 }
 
 void vlc_input_decoder_GetStatus( vlc_input_decoder_t *p_owner,
                                   struct vlc_input_decoder_status *status )
 {
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock(p_owner->p_fifo);
 
     status->format.changed = p_owner->b_fmt_description;
     p_owner->b_fmt_description = false;
@@ -2649,7 +2672,7 @@ void vlc_input_decoder_GetStatus( vlc_input_decoder_t *p_owner,
     }
     status->cc.desc = p_owner->cc.desc;
 
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
 }
 
 size_t vlc_input_decoder_GetFifoSize( vlc_input_decoder_t *p_owner )
@@ -2708,18 +2731,18 @@ int vlc_input_decoder_AddVoutOverlay( vlc_input_decoder_t *owner, subpicture_t *
     assert( owner->dec.fmt_in.i_cat == VIDEO_ES );
     assert( sub && channel );
 
-    vlc_mutex_lock( &owner->lock );
+    vlc_fifo_Lock(owner->p_fifo);
 
     if( !owner->p_vout )
     {
-        vlc_mutex_unlock( &owner->lock );
+        vlc_fifo_Unlock(owner->p_fifo);
         return VLC_EGENERIC;
     }
     ssize_t channel_id =
         vout_RegisterSubpictureChannel( owner->p_vout );
     if (channel_id == -1)
     {
-        vlc_mutex_unlock( &owner->lock );
+        vlc_fifo_Unlock(owner->p_fifo);
         return VLC_EGENERIC;
     }
     sub->i_start = sub->i_stop = vlc_tick_now();
@@ -2728,7 +2751,7 @@ int vlc_input_decoder_AddVoutOverlay( vlc_input_decoder_t *owner, subpicture_t *
     sub->b_ephemer = true;
     vout_PutSubpicture( owner->p_vout, sub );
 
-    vlc_mutex_unlock( &owner->lock );
+    vlc_fifo_Unlock(owner->p_fifo);
     return VLC_SUCCESS;
 }
 
@@ -2736,16 +2759,16 @@ int vlc_input_decoder_DelVoutOverlay( vlc_input_decoder_t *owner, size_t channel
 {
     assert( owner->dec.fmt_in.i_cat == VIDEO_ES );
 
-    vlc_mutex_lock( &owner->lock );
+    vlc_fifo_Lock(owner->p_fifo);
 
     if( !owner->p_vout )
     {
-        vlc_mutex_unlock( &owner->lock );
+        vlc_fifo_Unlock(owner->p_fifo);
         return VLC_EGENERIC;
     }
     vout_UnregisterSubpictureChannel( owner->p_vout, channel );
 
-    vlc_mutex_unlock( &owner->lock );
+    vlc_fifo_Unlock(owner->p_fifo);
     return VLC_SUCCESS;
 }
 
@@ -2760,15 +2783,15 @@ int vlc_input_decoder_SetSpuHighlight( vlc_input_decoder_t *p_owner,
                            SOUT_INPUT_SET_SPU_HIGHLIGHT, spu_hl );
 #endif
 
-    vlc_mutex_lock( &p_owner->lock );
+    vlc_fifo_Lock(p_owner->p_fifo);
     if( !p_owner->p_vout )
     {
-        vlc_mutex_unlock( &p_owner->lock );
+        vlc_fifo_Unlock(p_owner->p_fifo);
         return VLC_EGENERIC;
     }
 
     vout_SetSpuHighlight( p_owner->p_vout, spu_hl );
 
-    vlc_mutex_unlock( &p_owner->lock );
+    vlc_fifo_Unlock(p_owner->p_fifo);
     return VLC_SUCCESS;
 }


=====================================
src/misc/fifo.c
=====================================
@@ -44,6 +44,11 @@ struct vlc_fifo_t
 
 static_assert (offsetof (block_fifo_t, q) == 0, "Problems in <vlc_block.h>");
 
+bool vlc_fifo_Held(const block_fifo_t *fifo)
+{
+    return vlc_mutex_held(&fifo->q.lock);
+}
+
 size_t vlc_fifo_GetCount(const block_fifo_t *fifo)
 {
     vlc_mutex_assert(&fifo->q.lock);


=====================================
src/misc/picture_pool.c
=====================================
@@ -43,7 +43,6 @@ struct picture_pool_t {
     vlc_mutex_t lock;
     vlc_cond_t  wait;
 
-    bool               canceled;
     unsigned long long available;
     vlc_atomic_rc_t    refs;
     unsigned short     picture_count;
@@ -121,7 +120,6 @@ picture_pool_t *picture_pool_New(unsigned count, picture_t *const *tab)
     vlc_atomic_rc_init(&pool->refs);
     pool->picture_count = count;
     memcpy(pool->picture, tab, count * sizeof (picture_t *));
-    pool->canceled = false;
     return pool;
 }
 
@@ -180,28 +178,21 @@ error:
 
 picture_t *picture_pool_Get(picture_pool_t *pool)
 {
-    unsigned long long available;
 
     vlc_mutex_lock(&pool->lock);
     assert(vlc_atomic_rc_get(&pool->refs) > 0);
-    available = pool->available;
 
-    while (available != 0)
+    if (pool->available == 0)
     {
-        int i = ctz(available);
-
-        if (unlikely(pool->canceled))
-            break;
-
-        pool->available &= ~(1ULL << i);
         vlc_mutex_unlock(&pool->lock);
-        available &= ~(1ULL << i);
-
-        return picture_pool_ClonePicture(pool, i);
+        return NULL;
     }
 
+    int i = ctz(pool->available);
+    pool->available &= ~(1ULL << i);
     vlc_mutex_unlock(&pool->lock);
-    return NULL;
+
+    return picture_pool_ClonePicture(pool, i);
 }
 
 picture_t *picture_pool_Wait(picture_pool_t *pool)
@@ -210,14 +201,7 @@ picture_t *picture_pool_Wait(picture_pool_t *pool)
     assert(vlc_atomic_rc_get(&pool->refs) > 0);
 
     while (pool->available == 0)
-    {
-        if (pool->canceled)
-        {
-            vlc_mutex_unlock(&pool->lock);
-            return NULL;
-        }
         vlc_cond_wait(&pool->wait, &pool->lock);
-    }
 
     int i = ctz(pool->available);
     pool->available &= ~(1ULL << i);
@@ -226,17 +210,6 @@ picture_t *picture_pool_Wait(picture_pool_t *pool)
     return picture_pool_ClonePicture(pool, i);
 }
 
-void picture_pool_Cancel(picture_pool_t *pool, bool canceled)
-{
-    vlc_mutex_lock(&pool->lock);
-    assert(vlc_atomic_rc_get(&pool->refs) > 0);
-
-    pool->canceled = canceled;
-    if (canceled)
-        vlc_cond_broadcast(&pool->wait);
-    vlc_mutex_unlock(&pool->lock);
-}
-
 unsigned picture_pool_GetSize(const picture_pool_t *pool)
 {
     return pool->picture_count;


=====================================
test/Makefile.am
=====================================
@@ -28,6 +28,7 @@ check_PROGRAMS = \
 	test_src_input_stream \
 	test_src_input_stream_fifo \
 	test_src_input_thumbnail \
+	test_src_input_decoder \
 	test_src_player \
 	test_src_interface_dialog \
 	test_src_media_source \
@@ -190,6 +191,12 @@ test_modules_stream_out_pcr_sync_SOURCES = modules/stream_out/pcr_sync.c \
 	../modules/stream_out/transcode/pcr_sync.h
 test_modules_stream_out_pcr_sync_LDADD = $(LIBVLCCORE)
 
+test_src_input_decoder_SOURCES = \
+	src/input/decoder/input_decoder.c \
+	src/input/decoder/input_decoder.h \
+	src/input/decoder/input_decoder_scenarios.c
+test_src_input_decoder_LDADD = $(LIBVLCCORE) $(LIBVLC)
+
 checkall:
 	$(MAKE) check_PROGRAMS="$(check_PROGRAMS) $(EXTRA_PROGRAMS)" check
 


=====================================
test/src/input/decoder/input_decoder.c
=====================================
@@ -0,0 +1,353 @@
+/*****************************************************************************
+ * input_decoder.c: test for vlc_input_decoder state machine
+ *****************************************************************************
+ * Copyright (C) 2022 VideoLabs
+ *
+ * Author: Alexandre Janniaux <ajanni at videolabs.io>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+/* Define a builtin module for mocked parts */
+#define MODULE_NAME test_input_decoder_mock
+#define MODULE_STRING "test_input_decoder_mock"
+#undef __PLUGIN__
+
+const char vlc_module_name[] = MODULE_STRING;
+
+#include "../../../libvlc/test.h"
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_access.h>
+#include <vlc_demux.h>
+#include <vlc_codec.h>
+#include <vlc_window.h>
+#include <vlc_interface.h>
+#include <vlc_player.h>
+#include <vlc_filter.h>
+#include <vlc_threads.h>
+#include <vlc_vout_display.h>
+#include <vlc_sout.h>
+
+#include <limits.h>
+
+#include "input_decoder.h"
+static size_t current_scenario = 0;
+
+static vlc_cond_t player_cond = VLC_STATIC_COND;
+
+static void DecoderDeviceClose(struct vlc_decoder_device *device)
+    { VLC_UNUSED(device); }
+
+static const struct vlc_decoder_device_operations decoder_device_ops =
+{
+    .close = DecoderDeviceClose,
+};
+
+static int OpenDecoderDevice(
+        struct vlc_decoder_device *device,
+        vlc_window_t *window
+) {
+    VLC_UNUSED(window);
+    device->ops = &decoder_device_ops;
+    /* Pick any valid one, we'll not use the module which can make use of
+     * the private parts. */
+    device->type = VLC_DECODER_DEVICE_VAAPI;
+    return VLC_SUCCESS;
+}
+
+static int DecoderDecode(decoder_t *dec, block_t *block)
+{
+    if (block == NULL)
+        return VLC_SUCCESS;
+
+    const picture_resource_t resource = {
+        .p_sys = NULL,
+    };
+    picture_t *pic = picture_NewFromResource(&dec->fmt_out.video, &resource);
+    assert(pic);
+    pic->date = block->i_pts;
+    pic->b_progressive = true;
+    block_Release(block);
+
+    struct input_decoder_scenario *scenario = &input_decoder_scenarios[current_scenario];
+    assert(scenario->decoder_decode != NULL);
+    return scenario->decoder_decode(dec, pic);
+}
+
+static void DecoderFlush(decoder_t *dec)
+{
+    struct input_decoder_scenario *scenario = &input_decoder_scenarios[current_scenario];
+    assert(scenario->decoder_flush != NULL);
+    return scenario->decoder_flush(dec);
+}
+
+static void CloseDecoder(vlc_object_t *obj)
+{
+    decoder_t *dec = (decoder_t*)obj;
+    struct vlc_video_context *vctx = dec->p_sys;
+    if (vctx)
+        vlc_video_context_Release(vctx);
+}
+
+static int OpenDecoder(vlc_object_t *obj)
+{
+    decoder_t *dec = (decoder_t*)obj;
+
+    struct vlc_decoder_device *device = decoder_GetDecoderDevice(dec);
+    assert(device);
+    vlc_decoder_device_Release(device);
+
+    dec->pf_decode = DecoderDecode;
+    dec->pf_flush = DecoderFlush;
+    es_format_Clean(&dec->fmt_out);
+    es_format_Copy(&dec->fmt_out, &dec->fmt_in);
+
+    struct input_decoder_scenario *scenario = &input_decoder_scenarios[current_scenario];
+    assert(scenario->decoder_setup != NULL);
+    scenario->decoder_setup(dec);
+
+    msg_Dbg(obj, "Decoder chroma %4.4s -> %4.4s size %ux%u",
+            (const char *)&dec->fmt_in.i_codec,
+            (const char *)&dec->fmt_out.i_codec,
+            dec->fmt_out.video.i_width, dec->fmt_out.video.i_height);
+
+    return VLC_SUCCESS;
+}
+
+static void DisplayPrepare(vout_display_t *vd, picture_t *picture,
+        subpicture_t *subpic, vlc_tick_t date)
+{
+    (void)vd; (void)subpic; (void)date;
+
+    struct input_decoder_scenario *scenario = &input_decoder_scenarios[current_scenario];
+    assert(scenario->display_prepare != NULL);
+    scenario->display_prepare(vd, picture);
+}
+
+static int DisplayControl(vout_display_t *vd, int query)
+{
+    (void)vd; (void)query;
+    return VLC_SUCCESS;
+}
+
+static int OpenDisplay(vout_display_t *vd, video_format_t *fmtp, vlc_video_context *context)
+{
+    (void)fmtp; (void)context;
+
+    static const struct vlc_display_operations ops = {
+        .prepare = DisplayPrepare,
+        .control = DisplayControl,
+    };
+    vd->ops = &ops;
+
+    return VLC_SUCCESS;
+}
+
+static int OpenWindow(vlc_window_t *wnd)
+{
+    static const struct vlc_window_operations ops =
+    {
+        .resize = NULL,
+    };
+    wnd->ops = &ops;
+    return VLC_SUCCESS;
+}
+
+static void *SoutFilterAdd(sout_stream_t *stream, const es_format_t *fmt)
+{
+    (void)stream; (void)fmt;
+    void *id = malloc(1);
+    assert(id != NULL);
+    return id;
+}
+
+static void SoutFilterDel(sout_stream_t *stream, void *id)
+{
+    (void)stream;
+    free(id);
+}
+
+static int SoutFilterSend(sout_stream_t *stream, void *id, block_t *block)
+{
+    struct input_decoder_scenario *scenario = &input_decoder_scenarios[current_scenario];
+    if (scenario->sout_filter_send != NULL)
+        return scenario->sout_filter_send(stream, id, block);
+    block_Release(block);
+    return VLC_SUCCESS;
+}
+
+static void SoutFilterFlush(sout_stream_t *stream, void *id)
+{
+    struct input_decoder_scenario *scenario = &input_decoder_scenarios[current_scenario];
+    if (scenario->sout_filter_flush != NULL)
+        scenario->sout_filter_flush(stream, id);
+}
+
+static int OpenSoutFilter(vlc_object_t *obj)
+{
+    sout_stream_t *stream = (sout_stream_t *)obj;
+    static const struct sout_stream_operations ops = {
+        .add = SoutFilterAdd,
+        .del = SoutFilterDel,
+        .send = SoutFilterSend,
+        .flush = SoutFilterFlush,
+    };
+    stream->ops = &ops;
+    return VLC_SUCCESS;
+};
+
+static void on_state_changed(vlc_player_t *player, enum vlc_player_state state, void *opaque)
+{
+    (void)player; (void)state; (void) opaque;
+    vlc_cond_signal(&player_cond);
+}
+
+static void play_scenario(intf_thread_t *intf, struct input_decoder_scenario *scenario)
+{
+    input_decoder_scenario_init();
+    input_item_t *media = input_item_New(scenario->source, "dummy");
+    assert(media);
+
+    var_Create(intf, "codec", VLC_VAR_STRING);
+    var_SetString(intf, "codec", MODULE_STRING);
+
+    if (scenario->sout != NULL)
+    {
+        char *sout;
+        assert(asprintf(&sout, ":sout=%s", scenario->sout) != -1);
+        input_item_AddOption(media, sout, VLC_INPUT_OPTION_TRUSTED);
+        free(sout);
+    }
+
+    vlc_player_t *player = vlc_player_New(&intf->obj,
+        VLC_PLAYER_LOCK_NORMAL, NULL, NULL);
+    assert(player);
+
+    intf->p_sys = (intf_sys_t *)player;
+
+    static const struct vlc_player_cbs player_cbs = {
+        .on_state_changed = on_state_changed,
+    };
+
+    vlc_player_Lock(player);
+    vlc_player_listener_id *listener =
+        vlc_player_AddListener(player, &player_cbs, NULL);
+    vlc_player_SetCurrentMedia(player, media);
+    vlc_player_Start(player);
+    vlc_player_Unlock(player);
+
+    input_decoder_scenario_wait(intf, scenario);
+
+    vlc_player_Lock(player);
+    vlc_player_Stop(player);
+
+    while (vlc_player_GetState(player) != VLC_PLAYER_STATE_STOPPED)
+        vlc_player_CondWait(player, &player_cond);
+
+    vlc_player_RemoveListener(player, listener);
+    vlc_player_Unlock(player);
+
+    input_decoder_scenario_check(scenario);
+
+    vlc_player_Delete(player);
+    input_item_Release(media);
+
+    var_Destroy(intf, "codec");
+}
+
+static int OpenIntf(vlc_object_t *obj)
+{
+    intf_thread_t *intf = (intf_thread_t*)obj;
+
+    while (current_scenario < input_decoder_scenarios_count)
+    {
+        msg_Info(intf, " - Running scenario %zu", current_scenario);
+        play_scenario(intf, &input_decoder_scenarios[current_scenario]);
+        current_scenario++;
+    }
+
+    return VLC_SUCCESS;
+}
+
+/**
+ * Inject the mocked modules as a static plugin:
+ *  - access for triggering the correct decoder
+ *  - decoder for generating video format and context
+ *  - filter for generating video format and context
+ **/
+vlc_module_begin()
+    set_callback(OpenIntf)
+    set_capability("interface", 0)
+
+
+    add_submodule()
+        set_callback(OpenDecoderDevice)
+        set_capability("decoder device", 0)
+
+    add_submodule()
+        set_callbacks(OpenDecoder, CloseDecoder)
+        set_capability("video decoder", INT_MAX)
+
+    add_submodule()
+        set_callback(OpenWindow)
+        set_capability("vout window", INT_MAX)
+
+    add_submodule()
+        set_callback_display(OpenDisplay, 0)
+
+    add_submodule()
+        set_callback(OpenSoutFilter)
+        set_capability("sout output", 0)
+
+vlc_module_end()
+
+/* Helper typedef for vlc_static_modules */
+typedef int (*vlc_plugin_cb)(vlc_set_cb, void*);
+
+VLC_EXPORT vlc_plugin_cb vlc_static_modules[] = {
+    VLC_SYMBOL(vlc_entry),
+    NULL
+};
+
+int main( int argc, char **argv )
+{
+    (void)argc; (void)argv;
+    test_init();
+
+    const char * const args[] = {
+        "-vvv",
+        "--vout=" MODULE_STRING,
+        "--dec-dev=" MODULE_STRING,
+        "--aout=dummy",
+        "--text-renderer=dummy",
+        "--no-auto-preparse",
+        "--no-spu",
+        "--no-osd",
+    };
+
+    libvlc_instance_t *vlc = libvlc_new(ARRAY_SIZE(args), args);
+
+    libvlc_add_intf(vlc, MODULE_STRING);
+    libvlc_playlist_play(vlc);
+
+    libvlc_release(vlc);
+    assert(input_decoder_scenarios_count == current_scenario);
+    return 0;
+}


=====================================
test/src/input/decoder/input_decoder.h
=====================================
@@ -0,0 +1,48 @@
+/*****************************************************************************
+ * input_decoder.h: test for input_decoder state machine
+ *****************************************************************************
+ * Copyright (C) 2022 VideoLabs
+ *
+ * Author: Alexandre Janniaux <ajanni at videolabs.io>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#include <vlc_fourcc.h>
+
+#define TEST_FLAG_CONVERTER 0x01
+#define TEST_FLAG_FILTER 0x02
+
+typedef struct vout_display_t vout_display_t;
+typedef struct intf_thread_t intf_thread_t;
+
+struct input_decoder_scenario {
+    const char *source;
+    const char *sout;
+    void (*decoder_setup)(decoder_t *);
+    int (*decoder_decode)(decoder_t *, picture_t *);
+    void (*decoder_flush)(decoder_t *);
+    void (*display_prepare)(vout_display_t *vd, picture_t *pic);
+    void (*interface_setup)(intf_thread_t *intf);
+    int (*sout_filter_send)(sout_stream_t *stream, void *id, block_t *block);
+    void (*sout_filter_flush)(sout_stream_t *stream, void *id);
+};
+
+
+void input_decoder_scenario_init(void);
+void input_decoder_scenario_wait(intf_thread_t *intf, struct input_decoder_scenario *scenario);
+void input_decoder_scenario_check(struct input_decoder_scenario *scenario);
+extern size_t input_decoder_scenarios_count;
+extern struct input_decoder_scenario input_decoder_scenarios[];


=====================================
test/src/input/decoder/input_decoder_scenarios.c
=====================================
@@ -0,0 +1,313 @@
+/*****************************************************************************
+ * input_decoder_scenario.c: testflight for input_decoder state machine
+ *****************************************************************************
+ * Copyright (C) 2022 VideoLabs
+ *
+ * Author: Alexandre Janniaux <ajanni at videolabs.io>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#define MODULE_NAME test_input_decoder_mock
+#define MODULE_STRING "test_input_decoder_mock"
+#undef __PLUGIN__
+
+#include <vlc_common.h>
+#include <vlc_messages.h>
+#include <vlc_player.h>
+#include <vlc_interface.h>
+#include <vlc_codec.h>
+
+#include "input_decoder.h"
+
+static struct scenario_data
+{
+    vlc_sem_t wait_stop;
+    vlc_sem_t display_prepare_signal;
+    vlc_sem_t wait_ready_to_flush;
+    struct vlc_video_context *decoder_vctx;
+    bool skip_decoder;
+    bool stream_out_sent;
+} scenario_data;
+
+static void decoder_fixed_size(decoder_t *dec, vlc_fourcc_t chroma,
+        unsigned width, unsigned height)
+{
+    dec->fmt_out.video.i_chroma
+        = dec->fmt_out.i_codec
+        = chroma;
+    dec->fmt_out.video.i_visible_width
+        = dec->fmt_out.video.i_width
+        = width;
+    dec->fmt_out.video.i_visible_height
+        = dec->fmt_out.video.i_height
+        = height;
+}
+
+static void decoder_i420_800_600(decoder_t *dec)
+    { decoder_fixed_size(dec, VLC_CODEC_I420, 800, 600); }
+
+static int decoder_decode_check_cc(decoder_t *dec, picture_t *pic)
+{
+    vlc_tick_t date = pic->date;
+    picture_Release(pic);
+
+    block_t *p_cc = block_Alloc( 1 );
+    if (p_cc == NULL)
+        return VLC_ENOMEM;
+
+    p_cc->i_dts = p_cc->i_pts = date;
+
+    decoder_cc_desc_t desc = {
+        .i_608_channels = 1,
+    };
+    decoder_QueueCc( dec, p_cc, &desc );
+
+    vlc_sem_post(&scenario_data.wait_ready_to_flush);
+
+    return VLC_SUCCESS;
+}
+
+struct picture_watcher_context {
+    picture_context_t context;
+    vlc_sem_t wait_picture;
+    vlc_sem_t wait_prepare;
+    vlc_atomic_rc_t rc;
+};
+
+static void context_destroy(picture_context_t *context)
+{
+    struct picture_watcher_context *watcher =
+        container_of(context, struct picture_watcher_context, context);
+
+    if (vlc_atomic_rc_dec(&watcher->rc))
+        vlc_sem_post(&watcher->wait_picture);
+}
+
+static picture_context_t * context_copy(picture_context_t *context)
+{
+    struct picture_watcher_context *watcher =
+        container_of(context, struct picture_watcher_context, context);
+    vlc_atomic_rc_inc(&watcher->rc);
+    return context;
+}
+
+static int decoder_decode_check_flush_video(decoder_t *dec, picture_t *pic)
+{
+    if (scenario_data.skip_decoder)
+    {
+        picture_Release(pic);
+        return VLC_SUCCESS;
+    }
+
+    int ret = decoder_UpdateVideoOutput(dec, NULL);
+    assert(ret == VLC_SUCCESS);
+
+    struct picture_watcher_context context1 = {
+        .context.destroy = context_destroy,
+        .context.copy = context_copy,
+    };
+    vlc_sem_init(&context1.wait_picture, 0);
+    vlc_atomic_rc_init(&context1.rc);
+
+    picture_t *second_pic = picture_Clone(pic);
+    second_pic->b_force = true;
+    second_pic->date = VLC_TICK_0;
+    second_pic->b_progressive = true;
+    second_pic->context = &context1.context;
+
+    msg_Info(dec, "Send first frame from decoder to video output");
+    decoder_QueueVideo(dec, second_pic);
+
+    msg_Info(dec, "Wait for the display to prepare the frame");
+    vlc_sem_wait(&context1.wait_prepare);
+
+    msg_Info(dec, "Trigger decoder and vout flush");
+    vlc_sem_post(&scenario_data.wait_ready_to_flush);
+
+    /* Wait for the picture to be flushed by the vout. */
+    msg_Info(dec, "Wait for the picture to be flushed from the vout");
+    vlc_sem_wait(&context1.wait_picture);
+
+    /* Reinit the picture context waiter. */
+
+    struct picture_watcher_context context2 = {
+        .context.destroy = context_destroy,
+        .context.copy = context_copy,
+    };
+    vlc_sem_init(&context2.wait_picture, 0);
+    vlc_atomic_rc_init(&context2.rc);
+
+    picture_t *third_pic = picture_Clone(pic);
+    third_pic->b_force = true;
+    third_pic->date = VLC_TICK_0 + 1;
+    third_pic->b_progressive = true;
+    third_pic->context = &context2.context;
+
+    msg_Info(dec, "Re-queue the picture to the vout before decoder::pf_flush is called");
+    decoder_QueueVideo(dec, third_pic);
+
+    /* Wait for the picture to be flushed by the input decoder. */
+    msg_Info(dec, "Ensure the picture has been discarded by the input decoder");
+    vlc_sem_wait(&context2.wait_picture);
+
+    /* Ok, since the picture has been released, we should have decode succeed
+     * and we can now check that flush is called. */
+
+    picture_Release(pic);
+
+    msg_Info(dec, "Nothing to do from pf_decode, let pf_flush be called by the input decoder");
+    scenario_data.skip_decoder = true;
+    return VLC_SUCCESS;
+}
+
+static void decoder_flush_signal(decoder_t *dec)
+{
+    (void)dec;
+    vlc_sem_post(&scenario_data.wait_stop);
+}
+
+static void display_prepare_signal(vout_display_t *vd, picture_t *pic)
+{
+    (void)vd;
+
+    struct picture_watcher_context *watcher =
+        container_of(pic->context, struct picture_watcher_context, context);
+    vlc_sem_post(&watcher->wait_prepare);
+}
+
+static void PlayerOnTrackListChanged(vlc_player_t *player,
+        enum vlc_player_list_action action,
+        const struct vlc_player_track *track,
+        void *data)
+{
+    (void)data;
+
+    if (action != VLC_PLAYER_LIST_ADDED ||
+        track->fmt.i_cat != SPU_ES)
+        return;
+
+    vlc_player_SelectTrack(player, track, VLC_PLAYER_SELECT_EXCLUSIVE);
+}
+
+static void interface_setup_select_cc(intf_thread_t *intf)
+{
+    vlc_player_t *player = (vlc_player_t *)intf->p_sys;
+
+    static const struct vlc_player_cbs player_cbs =
+    {
+        .on_track_list_changed = PlayerOnTrackListChanged,
+    };
+
+    vlc_player_Lock(player);
+    vlc_player_listener_id *listener_id =
+        vlc_player_AddListener(player, &player_cbs, NULL);
+    vlc_player_Unlock(player);
+
+    vlc_sem_wait(&scenario_data.wait_ready_to_flush);
+    vlc_player_Lock(player);
+    vlc_player_SetPosition(player, 0);
+    vlc_player_Unlock(player);
+
+    vlc_sem_wait(&scenario_data.wait_ready_to_flush);
+    vlc_player_Lock(player);
+    vlc_player_RemoveListener(player, listener_id);
+    vlc_player_Unlock(player);
+}
+
+static void interface_setup_check_flush(intf_thread_t *intf)
+{
+    vlc_player_t *player = (vlc_player_t *)intf->p_sys;
+    vlc_sem_wait(&scenario_data.wait_ready_to_flush);
+
+    vlc_player_Lock(player);
+    vlc_player_SetPosition(player, 0);
+    vlc_player_Unlock(player);
+}
+
+static int sout_filter_send(sout_stream_t *stream, void *id, block_t *block)
+{
+    (void)stream; (void)id;
+    block_Release(block);
+    scenario_data.stream_out_sent = true;
+    vlc_sem_post(&scenario_data.wait_ready_to_flush);
+    return VLC_SUCCESS;
+}
+
+static void sout_filter_flush(sout_stream_t *stream, void *id)
+{
+    (void)stream; (void)id;
+    assert(scenario_data.stream_out_sent);
+    vlc_sem_post(&scenario_data.wait_stop);
+}
+
+const char source_800_600[] = "mock://video_track_count=1;length=100000000000;video_width=800;video_height=600";
+struct input_decoder_scenario input_decoder_scenarios[] =
+{{
+    .source = source_800_600,
+    .decoder_setup = decoder_i420_800_600,
+    .decoder_flush = decoder_flush_signal,
+    .decoder_decode = decoder_decode_check_cc,
+    .interface_setup = interface_setup_select_cc,
+},
+{
+    .source = source_800_600,
+    .decoder_setup = decoder_i420_800_600,
+    .decoder_decode = decoder_decode_check_flush_video,
+    .decoder_flush = decoder_flush_signal,
+    .display_prepare = display_prepare_signal,
+    .interface_setup = interface_setup_check_flush,
+},
+{
+    /* Check that stream output is also flushed:
+      - the test cannot work if the stream_out filter is not added
+      - sout_filter_send(), signal the interface that it can flush
+      - the interface change player position to trigger a flush
+      - the flush is signaled to the stream_out filter
+      - the stream_out filter signal the end of the test */
+    .source = source_800_600,
+    .sout = "#" MODULE_STRING,
+    .sout_filter_send = sout_filter_send,
+    .sout_filter_flush = sout_filter_flush,
+    .interface_setup = interface_setup_check_flush,
+}};
+size_t input_decoder_scenarios_count = ARRAY_SIZE(input_decoder_scenarios);
+
+void input_decoder_scenario_init(void)
+{
+    scenario_data.decoder_vctx = NULL;
+    scenario_data.skip_decoder = false;
+    scenario_data.stream_out_sent = false;
+    vlc_sem_init(&scenario_data.wait_stop, 0);
+    vlc_sem_init(&scenario_data.display_prepare_signal, 0);
+    vlc_sem_init(&scenario_data.wait_ready_to_flush, 0);
+}
+
+void input_decoder_scenario_wait(intf_thread_t *intf, struct input_decoder_scenario *scenario)
+{
+    if (scenario->interface_setup)
+        scenario->interface_setup(intf);
+
+    vlc_sem_wait(&scenario_data.wait_stop);
+}
+
+void input_decoder_scenario_check(struct input_decoder_scenario *scenario)
+{
+    (void)scenario;
+}



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/ec6721742c068dd6b1c2bf18421ac176e1b348ba...b510622c5c700214eee9c2b657edddd5b0024a62

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/ec6721742c068dd6b1c2bf18421ac176e1b348ba...b510622c5c700214eee9c2b657edddd5b0024a62
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list