[vlc-devel] [PATCH 11/11] mediacodec: add input/output threads, remove polling

Thomas Guillem thomas at gllm.fr
Wed Aug 26 18:10:08 CEST 2015


This commit improve MediaCodec performances and remove polling every 10 msecs
when DecodeCommon is waiting for an input or an output buffer.

This commit adds two threads:

- InThread: Wait for a new block queued by DecodeCommon, wait indefinitely for
  a MediaCodec input buffer (this can be cancelled by a flush), copy the block
  into the input buffer and queue it.

- OutThread: Once an input buffer is queued, wait indefinitely for a MediaCodec
  output buffer (this can be cancelled by a flush), queue this buffer to a Fifo
  that will be dequeued by DecodeCommon. When a new output buffer is available,
  notify the Decoder process that will call pf_decode with a NULL pp_block, and
  returns a new picture.

Both threads, and pf_decode call are locked by the same mutex. Only
dequeue_in/dequeue_out from InThread/OutThread are not locked since these
functions can block.
---
 modules/codec/omxil/mediacodec.c | 671 ++++++++++++++++++++++++++++++---------
 1 file changed, 513 insertions(+), 158 deletions(-)

diff --git a/modules/codec/omxil/mediacodec.c b/modules/codec/omxil/mediacodec.c
index bed8225..33b0a01 100644
--- a/modules/codec/omxil/mediacodec.c
+++ b/modules/codec/omxil/mediacodec.c
@@ -53,6 +53,7 @@
 extern void jni_EventHardwareAccelerationError(); // TODO REMOVE
 
 #define BLOCK_FLAG_CSD (0x01 << BLOCK_FLAG_PRIVATE_SHIFT)
+#define BLOCK_FLAG_NEW (0x02 << BLOCK_FLAG_PRIVATE_SHIFT)
 
 /* Codec Specific Data */
 struct csd
@@ -80,6 +81,14 @@ typedef void (*dec_on_flush_cb)(decoder_t *);
  */
 typedef int (*dec_process_output_cb)(decoder_t *, mc_api_out *, picture_t **, block_t **);
 
+struct out_fifo
+{
+    uint32_t begin;
+    uint32_t size;
+    uint32_t capacity;
+    mc_api_out *p_outs;
+};
+
 struct decoder_sys_t
 {
     mc_api *api;
@@ -87,7 +96,7 @@ struct decoder_sys_t
 
     const char *mime;
 
-    /* Codec Specific Data buffer: sent in PutInput after a start or a flush
+    /* Codec Specific Data buffer: sent in InThread after a start or a flush
      * with the BUFFER_FLAG_CODEC_CONFIG flag.*/
     block_t **pp_csd;
     size_t i_csd_count;
@@ -96,9 +105,7 @@ struct decoder_sys_t
     bool b_update_format;
     bool b_has_format;
 
-    bool decoded;
     bool error_state;
-    bool b_new_block;
     int64_t i_preroll_end;
 
     /* Specific Audio/Video callbacks */
@@ -106,6 +113,20 @@ struct decoder_sys_t
     dec_on_flush_cb         pf_on_flush;
     dec_process_output_cb   pf_process_output;
 
+    vlc_mutex_t     lock;
+    vlc_cond_t      cond;
+    vlc_cond_t      flush_cond;
+    vlc_cond_t      dec_cond;
+    vlc_thread_t    in_thread;
+    vlc_thread_t    out_thread;
+    block_t        *p_in_block;
+    struct out_fifo out_fifo;
+    bool            b_flush_in;
+    bool            b_flush_out;
+    bool            b_output_ready;
+    bool            b_mc_running;
+    bool            b_threads_running;
+
     union
     {
         struct
@@ -148,6 +169,11 @@ static void Audio_OnFlush(decoder_t *);
 static int Audio_ProcessOutput(decoder_t *, mc_api_out *, picture_t **, block_t **);
 static block_t *DecodeAudio(decoder_t *, block_t **);
 
+static int DecodeFlush(decoder_t *, bool);
+static void StopMediaCodec(decoder_t *);
+static void *InThread(void *);
+static void *OutThread(void *);
+
 static void InvalidateAllPictures(decoder_t *);
 static int InsertInflightPicture(decoder_t *, picture_t *, unsigned int );
 
@@ -182,6 +208,59 @@ vlc_module_begin ()
         add_shortcut( "mediacodec_jni" )
 vlc_module_end ()
 
+static int OutFifo_Init(struct out_fifo *p_fifo, uint32_t capacity)
+{
+    p_fifo->p_outs = malloc(capacity * sizeof(mc_api_out));
+    if (!p_fifo->p_outs)
+        return -1;
+    p_fifo->begin = p_fifo->size = 0;
+    p_fifo->capacity = capacity;
+    return 0;
+}
+
+static void OutFifo_Deinit(struct out_fifo *p_fifo)
+{
+    free(p_fifo->p_outs);
+}
+
+static bool OutFifo_IsFull(struct out_fifo *p_fifo)
+{
+    return p_fifo->size == p_fifo->capacity;
+}
+
+static int OutFifo_Put(struct out_fifo *p_fifo, mc_api_out *p_out)
+{
+    if (OutFifo_IsFull(p_fifo))
+        return -1;
+    uint32_t end = (p_fifo->begin + p_fifo->size) % p_fifo->capacity;
+    p_fifo->p_outs[end] = *p_out;
+    p_fifo->size++;
+    return 0;
+}
+
+static mc_api_out *OutFifo_Get(struct out_fifo *p_fifo)
+{
+    if (p_fifo->size == 0)
+        return NULL;
+    mc_api_out *p_out = &p_fifo->p_outs[p_fifo->begin];
+    p_fifo->begin = (p_fifo->begin + 1) % p_fifo->capacity;
+    p_fifo->size--;
+    return p_out;
+}
+
+static int OutFifo_Clear(struct out_fifo *p_fifo)
+{
+    struct mc_api_out *p_out, *p_out_cfg = NULL;
+
+    while ((p_out = OutFifo_Get(p_fifo)))
+    {
+        if (p_out->type == MC_OUT_TYPE_CONF)
+            p_out_cfg = p_out;
+    }
+    if (p_out_cfg)
+        OutFifo_Put(p_fifo, p_out_cfg);
+    return 0;
+}
 
 static void CSDFree(decoder_t *p_dec)
 {
@@ -197,7 +276,7 @@ static void CSDFree(decoder_t *p_dec)
     p_sys->i_csd_count = 0;
 }
 
-/* Create the p_sys->p_csd that will be sent via PutInput */
+/* Create the p_sys->p_csd that will be sent from InThread */
 static int CSDDup(decoder_t *p_dec, const struct csd *p_csd, size_t i_count)
 {
     decoder_sys_t *p_sys = p_dec->p_sys;
@@ -442,11 +521,6 @@ static void StopMediaCodec(decoder_t *p_dec)
 {
     decoder_sys_t *p_sys = p_dec->p_sys;
 
-    /* Invalidate all pictures that are currently in flight in order
-     * to prevent the vout from using destroyed output buffers. */
-    if (p_sys->api->b_direct_rendering)
-        InvalidateAllPictures(p_dec);
-
     p_sys->api->stop(p_sys->api);
     if (p_dec->fmt_in.i_cat == VIDEO_ES && p_sys->u.video.p_awh)
         AWindowHandler_releaseSurface(p_sys->u.video.p_awh, AWindow_Video);
@@ -461,6 +535,7 @@ static int OpenDecoder(vlc_object_t *p_this, pf_MediaCodecApi_init pf_init)
     decoder_sys_t *p_sys;
     mc_api *api;
     const char *mime = NULL;
+    bool b_late_opening = false;
 
     /* Video or Audio if "mediacodec-audio" bool is true */
     if (p_dec->fmt_in.i_cat != VIDEO_ES && (p_dec->fmt_in.i_cat != AUDIO_ES
@@ -539,6 +614,7 @@ static int OpenDecoder(vlc_object_t *p_this, pf_MediaCodecApi_init pf_init)
     {
         api->clean(api);
         free(api);
+        free(p_sys);
         return VLC_ENOMEM;
     }
     p_sys->api = api;
@@ -552,7 +628,12 @@ static int OpenDecoder(vlc_object_t *p_this, pf_MediaCodecApi_init pf_init)
     p_dec->fmt_out.audio = p_dec->fmt_in.audio;
     p_dec->b_need_packetized = true;
     p_sys->mime = mime;
-    p_sys->b_new_block = true;
+
+    vlc_mutex_init(&p_sys->lock);
+    vlc_cond_init(&p_sys->cond);
+    vlc_cond_init(&p_sys->flush_cond);
+    vlc_cond_init(&p_sys->dec_cond);
+    OutFifo_Init(&p_sys->out_fifo, 32);
 
     if (p_dec->fmt_in.i_cat == VIDEO_ES)
     {
@@ -583,14 +664,15 @@ static int OpenDecoder(vlc_object_t *p_this, pf_MediaCodecApi_init pf_init)
             {
                 msg_Warn(p_dec, "waiting for sps/pps for codec %4.4s",
                          (const char *)&p_dec->fmt_in.i_codec);
-                return VLC_SUCCESS;
+                b_late_opening = true;
+                break;
             }
         case VLC_CODEC_VC1:
             if (!p_dec->fmt_in.i_extra)
             {
                 msg_Warn(p_dec, "waiting for extra data for codec %4.4s",
                          (const char *)&p_dec->fmt_in.i_codec);
-                return VLC_SUCCESS;
+                b_late_opening = true;
             }
             break;
         }
@@ -620,21 +702,46 @@ static int OpenDecoder(vlc_object_t *p_this, pf_MediaCodecApi_init pf_init)
             {
                 msg_Warn(p_dec, "waiting for extra data for codec %4.4s",
                          (const char *)&p_dec->fmt_in.i_codec);
-                return VLC_SUCCESS;
+                b_late_opening = true;
             }
             break;
         }
         if (!p_sys->u.audio.i_channels && p_sys->u.audio.b_need_channels)
         {
             msg_Warn(p_dec, "waiting for valid channel count");
-            return VLC_SUCCESS;
+            b_late_opening = true;
         }
     }
 
-    if (StartMediaCodec(p_dec) == VLC_SUCCESS)
-        return VLC_SUCCESS;
-    else
+    vlc_mutex_lock(&p_sys->lock);
+
+    if (!b_late_opening && StartMediaCodec(p_dec) != VLC_SUCCESS)
+    {
         msg_Err(p_dec, "StartMediaCodec failed");
+        vlc_mutex_unlock(&p_sys->lock);
+        goto bailout;
+    }
+
+    p_sys->b_threads_running = true;
+    int i_in_thread = vlc_clone(&p_sys->in_thread, InThread, p_dec,
+                                VLC_THREAD_PRIORITY_LOW);
+    int i_out_thread = vlc_clone(&p_sys->out_thread, OutThread, p_dec,
+                                 VLC_THREAD_PRIORITY_LOW);
+    if (i_in_thread || i_out_thread)
+    {
+        msg_Err(p_dec, "vlc_clone failed");
+        p_sys->b_threads_running = false;
+        vlc_mutex_unlock(&p_sys->lock);
+        if (!i_in_thread)
+            vlc_join(p_sys->in_thread, NULL);
+        if (!i_out_thread)
+            vlc_join(p_sys->out_thread, NULL);
+        goto bailout;
+    }
+    vlc_mutex_unlock(&p_sys->lock);
+
+    return VLC_SUCCESS;
+
 bailout:
     CloseDecoder(p_this);
     return VLC_EGENERIC;
@@ -661,6 +768,26 @@ static void CloseDecoder(vlc_object_t *p_this)
     if (!p_sys)
         return;
 
+    vlc_mutex_lock(&p_sys->lock);
+    if (p_sys->b_threads_running)
+    {
+        p_sys->b_threads_running = false;
+        DecodeFlush(p_dec, false);
+        vlc_cond_broadcast(&p_sys->cond);
+        vlc_mutex_unlock(&p_sys->lock);
+        vlc_join(p_sys->out_thread, NULL);
+        vlc_join(p_sys->in_thread, NULL);
+    }
+    else
+        vlc_mutex_unlock(&p_sys->lock);
+
+    OutFifo_Deinit(&p_sys->out_fifo);
+
+    vlc_mutex_destroy(&p_sys->lock);
+    vlc_cond_destroy(&p_sys->cond);
+    vlc_cond_destroy(&p_sys->flush_cond);
+    vlc_cond_destroy(&p_sys->dec_cond);
+
     StopMediaCodec(p_dec);
 
     CSDFree(p_dec);
@@ -1024,205 +1151,433 @@ static void HEVCProcessBlock(decoder_t *p_dec, block_t *p_block,
     VLC_UNUSED(p_size_changed);
 }
 
-static int DecodeFlush(decoder_t *p_dec)
+static int DecodeFlush(decoder_t *p_dec, bool b_from_input)
 {
     decoder_sys_t *p_sys = p_dec->p_sys;
+    bool b_was_running = p_sys->b_mc_running;
+
+    p_sys->b_mc_running = false;
+    p_sys->b_flush_in = !b_from_input;
+    p_sys->b_flush_out = true;
+    p_sys->i_preroll_end = 0;
+    p_sys->b_output_ready = false;
+    /* Resend CODEC_CONFIG buffer after a flush */
+    p_sys->i_csd_send = 0;
 
-    if (p_sys->decoded || p_sys->i_csd_send > 0)
-    {
-        p_sys->pf_on_flush(p_dec);
+    p_sys->pf_on_flush(p_dec);
+
+    OutFifo_Clear(&p_sys->out_fifo);
+
+    if (b_was_running && p_sys->api->flush(p_sys->api) != VLC_SUCCESS)
+        return VLC_EGENERIC;
+
+    vlc_cond_broadcast(&p_sys->cond);
+
+    while (p_sys->b_threads_running && (p_sys->b_flush_in || p_sys->b_flush_out))
+        vlc_cond_wait(&p_sys->flush_cond, &p_sys->lock);
+
+    if (!p_sys->b_threads_running)
+        return VLC_EGENERIC;
 
-        p_sys->i_preroll_end = 0;
-        if (p_sys->api->flush(p_sys->api) != VLC_SUCCESS)
-            return VLC_EGENERIC;
-        /* resend CODEC_CONFIG buffer after a flush */
-        p_sys->i_csd_send = 0;
-    }
-    p_sys->decoded = false;
     return VLC_SUCCESS;
 }
 
-static int GetAndProcessOutput(decoder_t *p_dec, picture_t **pp_out_pic,
-                               block_t **pp_out_block, mtime_t i_timeout)
+static block_t *GetNextInput(decoder_sys_t *p_sys)
 {
-    decoder_sys_t *p_sys = p_dec->p_sys;
-    struct mc_api_out out;
-    int i_index, i_ret;
-
-    i_index = p_sys->api->dequeue_out(p_sys->api, i_timeout);
-    if (i_index >= 0 || i_index == MC_API_INFO_OUTPUT_FORMAT_CHANGED
-     || i_index == MC_API_INFO_OUTPUT_BUFFERS_CHANGED)
-        i_ret = p_sys->api->get_out(p_sys->api, i_index, &out);
-    else if (i_index == MC_API_INFO_TRYAGAIN)
-        i_ret = 0;
+    if (p_sys->i_csd_send < p_sys->i_csd_count)
+        return p_sys->pp_csd[p_sys->i_csd_send++];
     else
-        i_ret = -1;
-
-    if (i_ret != 1)
-        return i_ret;
-
-    return p_sys->pf_process_output(p_dec, &out, pp_out_pic,
-                                    pp_out_block);
+        return p_sys->p_in_block;
 }
 
-/**
- * DecodeCommon called from DecodeVideo or DecodeAudio.
- * It returns -1 in case of error, 0 otherwise. The output buffer is returned
- * in pp_out_pic for Video, and pp_out_block for Audio.
- */
-static int DecodeCommon(decoder_t *p_dec, block_t **pp_block,
-                        picture_t **pp_out_pic, block_t **pp_out_block)
+static void *InThread(void *data)
 {
+    decoder_t *p_dec = data;
     decoder_sys_t *p_sys = p_dec->p_sys;
-    block_t *p_block = pp_block ? *pp_block : NULL;
-    unsigned int i_attempts = 0;
-    jlong timeout = 0;
-    int i_output_ret = 0;
-    int i_input_ret = 0;
-    bool b_abort = false;
-    bool b_error = false;
-    bool b_new_block = p_block ? p_sys->b_new_block : false;
 
-    if (p_sys->error_state)
-        goto endclean;
-
-    if (b_new_block)
+    for (;;)
     {
-        int i_ret, i_flags = 0;
+        int i_index;
+        block_t *p_block = NULL;
+        bool b_config;
 
-        p_sys->b_new_block = false;
+        vlc_mutex_lock(&p_sys->lock);
 
-        if (p_block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED))
+        /* Wait for a flush or a new input buffer */
+        while (p_sys->b_threads_running && !p_sys->b_flush_in
+            && !(p_block = GetNextInput(p_sys)))
+            vlc_cond_wait(&p_sys->cond, &p_sys->lock);
+
+        b_config = p_block && (p_block->i_flags & BLOCK_FLAG_CSD);
+
+        if (p_sys->b_flush_in)
         {
-            if (DecodeFlush(p_dec) != VLC_SUCCESS)
-                b_error = true;
-            goto endclean;
+            if (p_sys->p_in_block)
+                block_Release(p_sys->p_in_block);
+            p_sys->p_in_block = NULL;
+
+            /* Acknowledge flushed state */
+            p_sys->b_flush_in = false;
+            vlc_cond_signal(&p_sys->flush_cond);
+            goto next;
         }
 
-        i_ret = p_sys->pf_on_new_block(p_dec, p_block, &i_flags);
-        if (i_ret != 1)
+        /* Check if thread is not stopped */
+        if (!p_sys->b_threads_running)
+            goto end;
+
+        assert(p_block);
+
+        if (!b_config && p_block->i_flags & BLOCK_FLAG_NEW)
         {
-            if (i_ret == -1)
-                b_error = true;
-            goto endclean;
+            int i_flags = 0;
+            int i_ret = p_sys->pf_on_new_block(p_dec, p_block, &i_flags);
+
+            p_block->i_flags &= ~BLOCK_FLAG_NEW;
+            if (i_ret == 1)
+            {
+                if (i_flags & (NEWBLOCK_FLAG_FLUSH|NEWBLOCK_FLAG_RESTART))
+                {
+                    msg_Warn(p_dec, "Flushing from InThread");
+
+                    /* Flush before restart to unblock OutThread */
+                    if (DecodeFlush(p_dec, true) != VLC_SUCCESS)
+                        goto end;
+
+                    if (i_flags & NEWBLOCK_FLAG_RESTART)
+                    {
+                        msg_Warn(p_dec, "Restarting from InThread");
+                        StopMediaCodec(p_dec);
+                        if (StartMediaCodec(p_dec) != VLC_SUCCESS)
+                            goto end;
+                    }
+                    goto next;
+                }
+            } else if (i_ret == 0)
+            {
+                /* Ignore current block */
+                block_Release(p_block);
+                p_sys->p_in_block = NULL;
+                vlc_cond_signal(&p_sys->dec_cond);
+                goto next;
+            }
+            else
+            {
+                msg_Err(p_dec, "OnNewBlock failed");
+                goto end;
+            }
+        }
+
+        if (!b_config)
+        {
+            /* Signal DecodeCommon that a new block can be queued */
+            p_sys->p_in_block = NULL;
+            vlc_cond_signal(&p_sys->dec_cond);
         }
-        if (i_flags & NEWBLOCK_FLAG_FLUSH)
+        if (!p_sys->api->b_started)
         {
-            if (DecodeFlush(p_dec) != VLC_SUCCESS)
-                goto endclean;
+            /* Abort if MediaCodec is not yet started */
+            if (!b_config)
+                block_Release(p_block);
+            goto next;
         }
 
-        if (i_flags & NEWBLOCK_FLAG_RESTART)
+
+        vlc_mutex_unlock(&p_sys->lock);
+        /* Wait for an input buffer. This function returns when a new input
+         * buffer is available or if input is flushed. */
+        i_index = p_sys->api->dequeue_in(p_sys->api, -1);
+        vlc_mutex_lock(&p_sys->lock);
+
+        /* Ignore dequeue_in errors caused by flush */
+        if (p_sys->b_flush_in)
         {
-            StopMediaCodec(p_dec);
-            if (StartMediaCodec(p_dec) != VLC_SUCCESS)
-                goto endclean;
+            if (p_block && !b_config)
+                block_Release(p_block);
+
+            /* If i_index >= 0, DecodeFlush didn't flush this buffer, so flush
+             * again to release it. */
+            if (i_index >= 0 && p_sys->api->flush(p_sys->api) != VLC_SUCCESS)
+                goto end;
+            goto next;
         }
-    }
-    if (!p_sys->api->b_started)
-        goto endclean;
 
-    do
-    {
-        if ((p_sys->i_csd_send < p_sys->i_csd_count || p_block)
-         && i_input_ret == 0)
+        if (i_index >= 0)
         {
-            block_t *p_in_block;
-            mtime_t i_ts;
-            int i_index;
+            mtime_t i_ts = 0;
+            p_sys->b_mc_running = true;
 
-            if (p_sys->i_csd_send < p_sys->i_csd_count)
+            if (!b_config)
             {
-                p_in_block = p_sys->pp_csd[p_sys->i_csd_send];
-                i_ts = 0;
-            }
-            else
-            {
-                p_in_block = p_block;
                 i_ts = p_block->i_pts;
                 if (!i_ts && p_block->i_dts)
                     i_ts = p_block->i_dts;
             }
 
-            i_index = p_sys->api->dequeue_in(p_sys->api, timeout);
-            if (i_index >= 0)
-                i_input_ret = p_sys->api->queue_in(p_sys->api, i_index,
-                                                   p_in_block->p_buffer,
-                                                   p_in_block->i_buffer, i_ts,
-                                                   p_in_block->i_flags & BLOCK_FLAG_CSD) == 0 ? 1 : -1;
-            else if (i_index == MC_API_INFO_TRYAGAIN)
-                i_input_ret = 0;
-            else
-                i_input_ret = -1;
-
-            if (i_input_ret == 1)
+            if (p_sys->api->queue_in(p_sys->api, i_index,
+                                     p_block->p_buffer, p_block->i_buffer, i_ts,
+                                     b_config) == 0)
             {
-                if (p_sys->i_csd_send < p_sys->i_csd_count)
+                if (!b_config)
                 {
-                    p_sys->i_csd_send++;
-                    i_input_ret = 0;
-                }
-                else
-                {
-                    p_sys->decoded = true;
                     if (p_block->i_flags & BLOCK_FLAG_PREROLL )
                         p_sys->i_preroll_end = i_ts;
+
+                    block_Release(p_block);
+                    /* One input buffer is queued, unblock OutThread that will
+                     * fetch output buffers */
+                    p_sys->b_output_ready = true;
+                    vlc_cond_broadcast(&p_sys->cond);
                 }
+            } else
+            {
+                msg_Err(p_dec, "queue_in failed");
+                goto end;
             }
-            if (!p_sys->decoded)
-                continue;
         }
+        else
+        {
+            msg_Err(p_dec, "dequeue_in failed");
+            goto end;
+        }
+next:
+        vlc_mutex_unlock(&p_sys->lock);
+        continue;
+end:
+        msg_Warn(p_dec, "InThread stopped");
+        p_sys->b_threads_running = false;
+        if (p_block && !b_config)
+            block_Release(p_block);
+        p_sys->p_in_block = NULL;
+        vlc_cond_signal(&p_sys->dec_cond);
+        vlc_cond_signal(&p_sys->flush_cond);
+        vlc_mutex_unlock(&p_sys->lock);
+        break;
+    }
+
+    return NULL;
+}
+
+static void *OutThread(void *data)
+{
+    decoder_t *p_dec = data;
+    decoder_sys_t *p_sys = p_dec->p_sys;
+    bool b_got_output = false;
+
+    for (;;)
+    {
+        int i_index;
+
+        vlc_mutex_lock(&p_sys->lock);
+
+        /* Wait output ready and free space in output FIFO */
+        while (p_sys->b_threads_running && !p_sys->b_flush_out
+            && (!p_sys->b_output_ready || OutFifo_IsFull(&p_sys->out_fifo)))
+            vlc_cond_wait(&p_sys->cond, &p_sys->lock);
+
+        if (p_sys->b_flush_out)
+        {
+            /* Acknowledge flushed state */
+            p_sys->b_flush_out = false;
+            b_got_output = false;
+            vlc_cond_broadcast(&p_sys->flush_cond);
+            goto next;
+        }
+
+        /* Check if thread is not stopped */
+        if (!p_sys->b_threads_running)
+            goto end;
 
-        if (i_input_ret != -1 && p_sys->decoded && i_output_ret == 0)
+        if (OutFifo_IsFull(&p_sys->out_fifo))
+            goto next;
+
+        vlc_mutex_unlock(&p_sys->lock);
+        /* Wait for an output buffer. This function returns when a new output
+         * is available or if output is flushed. Assume that MediaCodec is in
+         * bad state if waiting more than 3 seconds for an output. */
+        i_index = p_sys->api->dequeue_out(p_sys->api,
+                                          b_got_output ? -1 : INT64_C(3000000));
+        vlc_mutex_lock(&p_sys->lock);
+
+        /* Ignore dequeue_out errors caused by flush */
+        if (p_sys->b_flush_out)
+            goto next;
+
+        if (i_index >= 0 || i_index == MC_API_INFO_OUTPUT_FORMAT_CHANGED
+         || i_index == MC_API_INFO_OUTPUT_BUFFERS_CHANGED)
         {
-            i_output_ret = GetAndProcessOutput(p_dec, pp_out_pic, pp_out_block,
-                                               timeout);
+            struct mc_api_out out;
+            int i_ret = p_sys->api->get_out(p_sys->api, i_index, &out);
 
-            if (!p_sys->b_has_format && i_output_ret == 0 && i_input_ret == 0
-             && ++i_attempts > 100)
+            if (i_ret == 1)
             {
-                /* No output and no format, thereforce mediacodec didn't
-                 * produce any output or events yet. Don't wait indefinitely
-                 * and abort after 2seconds (100 * 2 * 10ms) without any data.
-                 * Indeed, MediaCodec can fail without throwing any exception
-                 * or error returns... */
-                msg_Err(p_dec, "No output/input for %lld ms, abort",
-                                i_attempts * timeout);
-                b_error = true;
-                break;
+                b_got_output = true;
+                /* Queue the new output buffer for DecodeCommon */
+                OutFifo_Put(&p_sys->out_fifo, &out);
+                vlc_cond_signal(&p_sys->dec_cond);
+                decoder_OnOutputReady(p_dec);
+            } else if (i_ret != 0)
+            {
+                msg_Err(p_dec, "get_out failed");
+                goto end;
             }
         }
-        timeout = 10 * 1000; // 10 ms
-        /* loop until either the input or the output are processed (i_input_ret
-         * or i_output_ret == 1 ) or caused an error (i_input_ret or
-         * i_output_ret == -1 )*/
-    } while (p_block && i_input_ret == 0 && i_output_ret == 0 && !b_abort);
+        else
+        {
+            if (i_index == MC_API_INFO_TRYAGAIN)
+            {
+                /* MediaCodec didn't produce any output or events in 3 seconds.
+                 * Don't wait indefinitely and abort since it can fail without
+                 * throwing any exception or error returns... */
+                msg_Err(p_dec, "No output for 3 seconds, abort");
+            }
+            else
+                msg_Err(p_dec, "dequeue_out failed");
+            goto end;
+        }
+next:
+        vlc_mutex_unlock(&p_sys->lock);
+        continue;
+end:
+        msg_Warn(p_dec, "OutThread stopped");
+        p_sys->b_threads_running = false;
+        vlc_cond_signal(&p_sys->dec_cond);
+        vlc_cond_signal(&p_sys->flush_cond);
+        vlc_mutex_unlock(&p_sys->lock);
+        break;
+    }
+
+    return NULL;
+}
+
+static int GetAndProcessOutput(decoder_t *p_dec, picture_t **pp_out_pic,
+                               block_t **pp_out_block)
+{
+    decoder_sys_t *p_sys = p_dec->p_sys;
+    bool b_was_full = OutFifo_IsFull(&p_sys->out_fifo);
+    struct mc_api_out *p_out = OutFifo_Get(&p_sys->out_fifo);
+
+    if (!p_out)
+        return 0;
 
-    if (i_input_ret == -1 || i_output_ret == -1)
+    /* Signal the OutThread that there is free space in the Fifo */
+    if (b_was_full)
+        vlc_cond_broadcast(&p_sys->cond);
+    return p_sys->pf_process_output(p_dec, p_out, pp_out_pic,
+                                    pp_out_block);
+}
+
+/**
+ * DecodeCommon called from DecodeVideo or DecodeAudio.
+ * It returns -1 in case of error, 0 otherwise. The output buffer is returned
+ * in pp_out_pic for Video, and pp_out_block for Audio.
+ */
+static int DecodeCommon(decoder_t *p_dec, block_t **pp_block,
+                        picture_t **pp_out_pic, block_t **pp_out_block)
+{
+    decoder_sys_t *p_sys = p_dec->p_sys;
+    block_t *p_block = pp_block ? *pp_block : NULL;
+    bool b_error = false;
+
+    vlc_mutex_lock(&p_sys->lock);
+
+    if (p_sys->error_state || !p_sys->b_threads_running)
     {
-        msg_Err(p_dec, "%s failed",
-                i_input_ret == -1 ? "PutInput" : "GetOutput");
         b_error = true;
+        goto end;
     }
 
-endclean:
+    if (p_block)
+    {
+        int i_out_ret = 0;
+        mtime_t i_deadline;
+
+        if (p_block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED))
+        {
+            block_Release(p_block);
+            *pp_block = NULL;
+
+            if (DecodeFlush(p_dec, false) != VLC_SUCCESS)
+            {
+                msg_Err(p_dec, "DecodeFlush failed");
+                b_error = true;
+                goto end;
+            }
+            goto end;
+        }
 
-    /* If pf_decode returns NULL, we'll get a new p_block from the next
-     * pf_decode call. Therefore we need to release the current one even if we
-     * couldn't process it (it happens in case or error or if MediaCodec is
-     * still not opened). We also must release the current p_block if we were
-     * able to process it. */
-    if (p_block && (i_output_ret != 1 || i_input_ret != 0))
+        i_deadline= p_sys->api->b_direct_rendering ?
+                    mdate() + INT64_C(500000) : -1;
+        /* Loop until p_sys->p_in_block or an output is processed */
+        while (p_sys->b_threads_running
+            && !(i_out_ret = GetAndProcessOutput(p_dec, pp_out_pic, pp_out_block))
+            && p_sys->p_in_block)
+        {
+            if (i_deadline != -1)
+            {
+                /* HACK: When direct rendering is enabled, there is a possible
+                 * deadlock between the Decoder and the Vout. It happens when
+                 * the Vout is paused and when the Decoder is flushing. In that
+                 * case, the Vout won't release any output buffers, therefore
+                 * MediaCodec won't dequeue any input buffers. To work around
+                 * this issue, release all output buffers if DecodeCommon is
+                 * waiting more than 500 msec for a new input buffer. */
+                int i_ret = vlc_cond_timedwait(&p_sys->dec_cond, &p_sys->lock,
+                                               i_deadline);
+                if (i_ret == ETIMEDOUT)
+                {
+                    msg_Warn(p_dec, "Decoder stuck: invalidate all buffers");
+                    InvalidateAllPictures(p_dec);
+                    i_deadline = -1;
+                }
+            }
+            else
+                vlc_cond_wait(&p_sys->dec_cond, &p_sys->lock);
+        }
+
+        if (i_out_ret == -1)
+        {
+            msg_Err(p_dec, "GetAndProcessOutput failed");
+            b_error = true;
+            goto end;
+        }
+        if (!p_sys->p_in_block)
+        {
+            /* Queue a new input block */
+            *pp_block = NULL;
+            p_block->i_flags |= BLOCK_FLAG_NEW;
+            p_sys->p_in_block = p_block;
+            vlc_cond_broadcast(&p_sys->cond);
+        }
+    }
+    else
     {
-        block_Release(p_block);
-        *pp_block = NULL;
-        p_sys->b_new_block = true;
+        /* Try to process an output without waiting. Indeed, there is no
+         * p_block, so pf_decode can return immediately without any
+         * input/output processed. */
+        if (GetAndProcessOutput(p_dec, pp_out_pic, pp_out_block) == -1)
+        {
+            msg_Err(p_dec, "GetAndProcessOutput failed");
+            b_error = true;
+        }
     }
-    if (b_error && !p_sys->error_state) {
-        /* Signal the error to the Java. */
-        jni_EventHardwareAccelerationError();
-        p_sys->error_state = true;
+
+end:
+    if (b_error)
+    {
+        if (*pp_block)
+        {
+            block_Release(*pp_block);
+            *pp_block = NULL;
+        }
+        if (!p_sys->error_state) {
+            /* Signal the error to the Java. */
+            jni_EventHardwareAccelerationError();
+            p_sys->error_state = true;
+            p_sys->b_threads_running = false;
+        }
     }
+    vlc_mutex_unlock(&p_sys->lock);
 
     return b_error ? -1 : 0;
 }
-- 
2.1.4



More information about the vlc-devel mailing list