static void
gst_vaapidecode_purge (GstVaapiDecode * decode)
{
  GstVaapiDecoderStatus status;

  if (!decode->decoder)
    return;

  status = gst_vaapi_decoder_flush (decode->decoder);
  if (status != GST_VAAPI_DECODER_STATUS_SUCCESS)
    GST_INFO_OBJECT (decode, "failed to flush decoder (status %d)", status);

  /* Purge all decoded frames as we don't need them (e.g. flush and close)
   * Releasing the frames is important, otherwise the frames are not
   * freed. */
  do {
    GstVideoCodecFrame *frame = NULL;

    status =
        gst_vaapi_decoder_get_frame_with_timeout (decode->decoder, &frame, 0);
    if (frame) {
      gst_video_decoder_release_frame (GST_VIDEO_DECODER (decode), frame);
      gst_video_codec_frame_unref (frame);
    }
  } while (status == GST_VAAPI_DECODER_STATUS_SUCCESS);
}
static GstFlowReturn
gst_vaapidecode_finish (GstVideoDecoder * vdec)
{
  GstVaapiDecode *const decode = GST_VAAPIDECODE (vdec);
  GstVaapiDecoderStatus status;
  GstFlowReturn ret;

  if (!decode->decoder)
    return GST_FLOW_OK;

  gst_vaapidecode_flush_output_adapter (decode);
  status = gst_vaapi_decoder_flush (decode->decoder);
  ret = gst_vaapidecode_push_all_decoded_frames (decode);
  if (status != GST_VAAPI_DECODER_STATUS_SUCCESS)
    goto error_decoder_flush;
  return ret;

  /* ERRORS: */
error_decoder_flush:
  {
    GST_WARNING_OBJECT (decode, "failed to flush decoder (status %d)", status);
    return GST_FLOW_ERROR;
  }
}
static gboolean
gst_vaapidecode_internal_flush (GstVideoDecoder * vdec)
{
  GstVaapiDecode *const decode = GST_VAAPIDECODE (vdec);
  GstVaapiDecoderStatus status;

  if (!decode->decoder)
    return TRUE;

  /* If there is something in GstVideoDecoder's output adapter, then
     submit the frame for decoding */
  if (decode->current_frame_size) {
    gst_video_decoder_have_frame (vdec);
    decode->current_frame_size = 0;
  }

  status = gst_vaapi_decoder_flush (decode->decoder);
  if (status != GST_VAAPI_DECODER_STATUS_SUCCESS) {
    GST_WARNING_OBJECT (decode, "failed to flush decoder (status %d)", status);
    return FALSE;
  }

  return TRUE;
}
static gpointer
decoder_thread (gpointer data)
{
  App *const app = data;
  GError *error = NULL;
  GstVaapiDecoderStatus status;
  GstVaapiSurfaceProxy *proxy;
  RenderFrame *rfp;
  GstBuffer *buffer;
  GstClockTime pts;
  gboolean got_surface, got_eos = FALSE;
  gint64 end_time;
  guint ofs;

  g_print ("Decoder thread started\n");

#define SEND_ERROR(...)                                                 \
    do {                                                                \
        error = g_error_new(APP_ERROR, APP_ERROR_DECODER, __VA_ARGS__); \
        goto send_error;                                                \
    } while (0)

  pts = g_get_monotonic_time ();
  ofs = 0;
  while (!app->decoder_thread_cancel) {
    if (G_UNLIKELY (ofs == app->file_size))
      buffer = NULL;
    else {
      const gsize size = MIN (4096, app->file_size - ofs);
      buffer = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY,
          app->file_data, app->file_size, ofs, size, NULL, NULL);
      if (!buffer)
        SEND_ERROR ("failed to allocate new buffer");
      ofs += size;
    }
    if (!gst_vaapi_decoder_put_buffer (app->decoder, buffer))
      SEND_ERROR ("failed to push buffer to decoder");
    gst_buffer_replace (&buffer, NULL);

  get_surface:
    status = gst_vaapi_decoder_get_surface (app->decoder, &proxy);
    switch (status) {
      case GST_VAAPI_DECODER_STATUS_SUCCESS:
        gst_vaapi_surface_proxy_set_destroy_notify (proxy,
            (GDestroyNotify) decoder_release, app);
        rfp = render_frame_new ();
        if (!rfp)
          SEND_ERROR ("failed to allocate render frame");
        rfp->proxy = proxy;
        rfp->pts = pts;
        rfp->duration = app->frame_duration;
        pts += app->frame_duration;
        g_async_queue_push (app->decoder_queue, rfp);
        break;
      case GST_VAAPI_DECODER_STATUS_ERROR_NO_DATA:
        /* nothing to do, just continue to the next iteration */
        break;
      case GST_VAAPI_DECODER_STATUS_END_OF_STREAM:
        gst_vaapi_decoder_flush (app->decoder);
        if (got_eos)
          goto send_eos;
        got_eos = TRUE;
        break;
      case GST_VAAPI_DECODER_STATUS_ERROR_NO_SURFACE:
        end_time = g_get_monotonic_time () + G_TIME_SPAN_SECOND;
        g_mutex_lock (&app->mutex);
        got_surface = g_cond_wait_until (&app->decoder_ready, &app->mutex,
            end_time);
        g_mutex_unlock (&app->mutex);
        if (got_surface)
          goto get_surface;
        SEND_ERROR ("failed to acquire a surface within one second");
        break;
      default:
        SEND_ERROR ("%s", get_decoder_status_string (status));
        break;
    }
  }
  return NULL;

#undef SEND_ERROR

send_eos:
  app_send_eos (app);
  return NULL;

send_error:
  app_send_error (app, error);
  return NULL;
}