nvdec: New plugin for NVIDIA hardware video decode
authorPer-Erik Brodin <per-erik.brodin@ericsson.com>
Wed, 3 May 2017 00:21:43 +0000 (17:21 -0700)
committerSebastian Dröge <sebastian@centricular.com>
Tue, 27 Jun 2017 05:59:59 +0000 (08:59 +0300)
https://bugzilla.gnome.org/show_bug.cgi?id=781537

configure.ac
sys/Makefile.am
sys/nvdec/Makefile.am [new file with mode: 0644]
sys/nvdec/gstnvdec.c [new file with mode: 0644]
sys/nvdec/gstnvdec.h [new file with mode: 0644]
sys/nvdec/plugin.c [new file with mode: 0644]

index d11df01..96abce4 100644 (file)
@@ -1912,9 +1912,9 @@ AC_SUBST(LIBUDEV_LIBS)
 AC_SUBST(LIBUSB_CFLAGS)
 AC_SUBST(LIBUSB_LIBS)
 
-dnl *** NVENC ***
-translit(dnm, m, l) AM_CONDITIONAL(USE_NVENC, true)
-AG_GST_CHECK_FEATURE(NVENC, [NVIDIA Encode API], nvenc, [
+dnl *** CUDA ***
+translit(dnm, m, l) AM_CONDITIONAL(USE_CUDA, true)
+AG_GST_CHECK_FEATURE(CUDA, [NVIDIA CUDA API],, [
   AC_ARG_WITH([cuda-prefix],
           AS_HELP_STRING([--with-cuda-prefix],
           [Use the provided prefix for detecting the cuda installation]),
@@ -1958,7 +1958,35 @@ AG_GST_CHECK_FEATURE(NVENC, [NVIDIA Encode API], nvenc, [
   AC_CHECK_LIB(cuda,cuInit,[HAVE_CUDA_LIB="yes"], [
       AC_MSG_WARN([Could not find cuda library])])
   LIBS="$save_LIBS"
+])
+
+dnl *** NVDEC ***
+translit(dnm, m, l) AM_CONDITIONAL(USE_NVDEC, true)
+AG_GST_CHECK_FEATURE(NVDEC, [nvdec], nvdec, [
+  HAVE_NVCUVID_H=no
+  save_CPPFLAGS="$CPPFLAGS"
+  CPPFLAGS="$CUDA_CFLAGS $save_CPPFLAGS"
+  AC_CHECK_HEADER([nvcuvid.h], [HAVE_NVCUVID_H=yes],
+      AC_MSG_WARN([Could not find nvcuvid.h]))
+  CPPFLAGS=$save_CPPFLAGS
+
+  HAVE_NVCUVID=no
+  save_LIBS="$LIBS"
+  LIBS="$CUDA_LIBS $save_LIBS"
+  AC_CHECK_LIB(nvcuvid, cuvidCtxLock, [HAVE_NVCUVID=yes],
+      AC_MSG_WARN([Could not find library nvcuvid]))
+  LIBS="$save_LIBS"
 
+  if test "x$HAVE_NVCUVID_H" = "xyes" -a "x$HAVE_NVCUVID" = "xyes"; then
+    HAVE_NVDEC=yes
+  else
+    HAVE_NVDEC=no
+  fi
+])
+
+dnl *** NVENC ***
+translit(dnm, m, l) AM_CONDITIONAL(USE_NVENC, true)
+AG_GST_CHECK_FEATURE(NVENC, [NVIDIA Encode API], nvenc, [
   dnl nvEncodeAPI.h header
   HAVE_NVENCODEAPI_H=no
   AC_ARG_VAR(NVENCODE_CFLAGS, [C compiler flags for NvEncodeAPI.h])
@@ -3628,6 +3656,7 @@ sys/dvb/Makefile
 sys/fbdev/Makefile
 sys/kms/Makefile
 sys/msdk/Makefile
+sys/nvdec/Makefile
 sys/nvenc/Makefile
 sys/opensles/Makefile
 sys/shm/Makefile
index e5aa704..ef05fb2 100644 (file)
@@ -106,6 +106,12 @@ else
 UVCH264_DIR=
 endif
 
+if USE_NVDEC
+NVDEC_DIR=nvdec
+else
+NVDEC_DIR=
+endif
+
 if USE_NVENC
 NVENC_DIR=nvenc
 else
@@ -124,10 +130,10 @@ else
 MSDK_DIR=
 endif
 
-SUBDIRS = $(ACM_DIR) $(ANDROID_MEDIA_DIR) $(APPLE_MEDIA_DIR) $(BLUEZ_DIR) $(D3DVIDEOSINK_DIR) $(DECKLINK_DIR) $(DIRECTSOUND_DIR) $(WINKS_DIR) $(DVB_DIR) $(FBDEV_DIR) $(KMS_DIR) $(OPENSLES_DIR) $(SHM_DIR) $(UVCH264_DIR) $(VCD_DIR) $(VDPAU_DIR) $(WININET_DIR) $(WINSCREENCAP_DIR) $(WASAPI_DIR) $(NVENC_DIR) $(TINYALSA_DIR) $(MSDK_DIR)
+SUBDIRS = $(ACM_DIR) $(ANDROID_MEDIA_DIR) $(APPLE_MEDIA_DIR) $(BLUEZ_DIR) $(D3DVIDEOSINK_DIR) $(DECKLINK_DIR) $(DIRECTSOUND_DIR) $(WINKS_DIR) $(DVB_DIR) $(FBDEV_DIR) $(KMS_DIR) $(OPENSLES_DIR) $(SHM_DIR) $(UVCH264_DIR) $(VCD_DIR) $(VDPAU_DIR) $(WININET_DIR) $(WINSCREENCAP_DIR) $(WASAPI_DIR) $(NVDEC_DIR) $(NVENC_DIR) $(TINYALSA_DIR) $(MSDK_DIR)
 
 DIST_SUBDIRS = acmenc acmmp3dec androidmedia applemedia bluez d3dvideosink decklink directsound dvb fbdev kms dshowdecwrapper dshowsrcwrapper dshowvideosink \
                opensles shm uvch264 vcd vdpau wasapi winks winscreencap \
-               nvenc tinyalsa msdk
+               nvdec nvenc tinyalsa msdk
 
 include $(top_srcdir)/common/parallel-subdirs.mak
diff --git a/sys/nvdec/Makefile.am b/sys/nvdec/Makefile.am
new file mode 100644 (file)
index 0000000..0a314b8
--- /dev/null
@@ -0,0 +1,24 @@
+plugin_LTLIBRARIES = libgstnvdec.la
+
+libgstnvdec_la_SOURCES = \
+       gstnvdec.c \
+       plugin.c
+
+noinst_HEADERS = \
+       gstnvdec.h
+
+libgstnvdec_la_CFLAGS = \
+       -I$(top_srcdir)/gst-libs \
+       $(GST_CFLAGS) \
+       $(GST_PBUTILS_CFLAGS) \
+       $(GST_VIDEO_CFLAGS) \
+       $(CUDA_CFLAGS)
+
+libgstnvdec_la_LIBADD = \
+       $(GST_LIBS) \
+       $(GST_PBUTILS_LIBS) \
+       $(GST_VIDEO_LIBS) \
+       $(CUDA_LIBS) -lnvcuvid \
+       $(top_builddir)/gst-libs/gst/gl/libgstgl-$(GST_API_VERSION).la
+
+libgstnvdec_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
diff --git a/sys/nvdec/gstnvdec.c b/sys/nvdec/gstnvdec.c
new file mode 100644 (file)
index 0000000..07ac0be
--- /dev/null
@@ -0,0 +1,1054 @@
+/*
+ * Copyright (C) 2017 Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstnvdec.h"
+
+#include <cudaGL.h>
+
+typedef enum
+{
+  GST_NVDEC_QUEUE_ITEM_TYPE_SEQUENCE,
+  GST_NVDEC_QUEUE_ITEM_TYPE_DECODE,
+  GST_NVDEC_QUEUE_ITEM_TYPE_DISPLAY
+} GstNvDecQueueItemType;
+
+typedef struct _GstNvDecQueueItem
+{
+  GstNvDecQueueItemType type;
+  gpointer data;
+} GstNvDecQueueItem;
+
+GST_DEBUG_CATEGORY_STATIC (gst_nvdec_debug_category);
+#define GST_CAT_DEFAULT gst_nvdec_debug_category
+
+static inline gboolean
+cuda_OK (CUresult result)
+{
+  const gchar *error_name, *error_text;
+
+  if (result != CUDA_SUCCESS) {
+    cuGetErrorName (result, &error_name);
+    cuGetErrorString (result, &error_text);
+    GST_WARNING ("CUDA call failed: %s, %s", error_name, error_text);
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+G_DEFINE_TYPE (GstNvDecCudaContext, gst_nvdec_cuda_context, G_TYPE_OBJECT);
+
+static void
+gst_nvdec_cuda_context_finalize (GObject * object)
+{
+  GstNvDecCudaContext *self = (GstNvDecCudaContext *) object;
+
+  if (self->lock) {
+    GST_DEBUG ("destroying CUDA context lock");
+    if (cuda_OK (cuvidCtxLockDestroy (self->lock)))
+      self->lock = NULL;
+    else
+      GST_ERROR ("failed to destroy CUDA context lock");
+  }
+
+  if (self->context) {
+    GST_DEBUG ("destroying CUDA context");
+    if (cuda_OK (cuCtxDestroy (self->context)))
+      self->context = NULL;
+    else
+      GST_ERROR ("failed to destroy CUDA context");
+  }
+
+  G_OBJECT_CLASS (gst_nvdec_cuda_context_parent_class)->finalize (object);
+}
+
+static void
+gst_nvdec_cuda_context_class_init (GstNvDecCudaContextClass * klass)
+{
+  G_OBJECT_CLASS (klass)->finalize = gst_nvdec_cuda_context_finalize;
+}
+
+static void
+gst_nvdec_cuda_context_init (GstNvDecCudaContext * self)
+{
+  if (!cuda_OK (cuInit (0)))
+    GST_ERROR ("failed to init CUDA");
+
+  if (!cuda_OK (cuCtxCreate (&self->context, CU_CTX_SCHED_AUTO, 0)))
+    GST_ERROR ("failed to create CUDA context");
+
+  if (!cuda_OK (cuCtxPopCurrent (NULL)))
+    GST_ERROR ("failed to pop current CUDA context");
+
+  if (!cuda_OK (cuvidCtxLockCreate (&self->lock, self->context)))
+    GST_ERROR ("failed to create CUDA context lock");
+}
+
+typedef struct _GstNvDecCudaGraphicsResourcesMeta
+{
+  GstMeta meta;
+
+  GstNvDecCudaContext *cuda_context;
+  CUgraphicsResource *resources;
+  guint num_resources;
+} GstNvDecCudaGraphicsResourcesMeta;
+
+GType gst_nvdec_cuda_graphics_resources_meta_api_get_type (void);
+#define GST_NVDEC_CUDA_GRAPHICS_RESOURCES_META_API_TYPE (gst_nvdec_cuda_graphics_resources_meta_api_get_type())
+#define gst_buffer_get_nvdec_cuda_graphics_resources_meta(b) \
+    ((GstNvDecCudaGraphicsResourcesMeta *)gst_buffer_get_meta((b), GST_NVDEC_CUDA_GRAPHICS_RESOURCES_META_API_TYPE))
+
+GType
+gst_nvdec_cuda_graphics_resources_meta_api_get_type (void)
+{
+  static volatile GType type;
+  static const gchar *tags[] = { GST_META_TAG_MEMORY_STR, NULL };
+
+  if (g_once_init_enter (&type)) {
+    GType _type =
+        gst_meta_api_type_register ("GstNvDecCudaGraphicsResourcesMetaAPI",
+        tags);
+    g_once_init_leave (&type, _type);
+  }
+
+  return type;
+}
+
+const GstMetaInfo *gst_nvdec_cuda_graphics_resources_meta_get_info (void);
+#define GST_NVDEC_CUDA_GRAPHICS_RESOURCES_META_INFO (gst_nvdec_cuda_graphics_resources_meta_get_info())
+
+GstNvDecCudaGraphicsResourcesMeta
+    * gst_buffer_add_nvdec_cuda_graphics_resources_meta (GstBuffer * buffer,
+    GstNvDecCudaContext * cuda_context);
+
+static void
+add_cgr_meta (GstGLContext * context, GstBuffer * buffer)
+{
+  GstNvDecCudaGraphicsResourcesMeta *meta;
+  GstMemory *mem;
+  GstMapInfo map_info = GST_MAP_INFO_INIT;
+  CUgraphicsResource *resources;
+  guint n, i, texture_id;
+
+  meta = gst_buffer_get_nvdec_cuda_graphics_resources_meta (buffer);
+  n = gst_buffer_n_memory (buffer);
+  resources = g_new0 (CUgraphicsResource, n);
+
+  if (!cuda_OK (cuvidCtxLock (meta->cuda_context->lock, 0)))
+    GST_WARNING ("failed to lock CUDA context");
+
+  for (i = 0; i < n; i++) {
+    mem = gst_buffer_get_memory (buffer, i);
+
+    if (gst_memory_map (mem, &map_info, GST_MAP_READ | GST_MAP_GL)) {
+      texture_id = *(guint *) map_info.data;
+
+      if (!cuda_OK (cuGraphicsGLRegisterImage (&resources[i], texture_id,
+                  GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD)))
+        GST_WARNING ("failed to register texture with CUDA");
+
+      gst_memory_unmap (mem, &map_info);
+    } else
+      GST_WARNING ("failed to map memory");
+
+    gst_memory_unref (mem);
+  }
+
+  if (!cuda_OK (cuvidCtxUnlock (meta->cuda_context->lock, 0)))
+    GST_WARNING ("failed to unlock CUDA context");
+
+  meta->resources = resources;
+  meta->num_resources = n;
+}
+
+static void
+free_cgr_meta (GstGLContext * context, GstNvDecCudaGraphicsResourcesMeta * meta)
+{
+  guint i;
+
+  if (!cuda_OK (cuvidCtxLock (meta->cuda_context->lock, 0)))
+    GST_WARNING ("failed to lock CUDA context");
+
+  for (i = 0; i < meta->num_resources; i++) {
+    if (!cuda_OK (cuGraphicsUnregisterResource ((const CUgraphicsResource)
+                meta->resources[i])))
+      GST_WARNING ("failed to unregister resource");
+  }
+
+  if (!cuda_OK (cuvidCtxUnlock (meta->cuda_context->lock, 0)))
+    GST_WARNING ("failed to unlock CUDA context");
+
+  meta->num_resources = 0;
+  g_free (meta->resources);
+  meta->resources = NULL;
+  g_object_unref (meta->cuda_context);
+  meta->cuda_context = NULL;
+}
+
+static gboolean
+gst_nvdec_cuda_graphics_resources_meta_init (GstMeta * meta, gpointer params,
+    GstBuffer * buffer)
+{
+  GstNvDecCudaGraphicsResourcesMeta *cgrmeta =
+      (GstNvDecCudaGraphicsResourcesMeta *) meta;
+  cgrmeta->cuda_context = NULL;
+  cgrmeta->resources = NULL;
+  cgrmeta->num_resources = 0;
+
+  return TRUE;
+}
+
+static gboolean
+gst_nvdec_cuda_graphics_resources_meta_transform (GstBuffer * transbuf,
+    GstMeta * meta, GstBuffer * buffer, GQuark type, gpointer data)
+{
+  return FALSE;
+}
+
+static void
+gst_nvdec_cuda_graphics_resources_meta_free (GstMeta * meta, GstBuffer * buffer)
+{
+  GstMemory *mem = gst_buffer_get_memory (buffer, 0);
+  gst_gl_context_thread_add (GST_GL_BASE_MEMORY_CAST (mem)->context,
+      (GstGLContextThreadFunc) free_cgr_meta, meta);
+  gst_memory_unref (mem);
+}
+
+const GstMetaInfo *
+gst_nvdec_cuda_graphics_resources_meta_get_info (void)
+{
+  static const GstMetaInfo *meta_info = NULL;
+
+  if (g_once_init_enter ((GstMetaInfo **) & meta_info)) {
+    const GstMetaInfo *mi =
+        gst_meta_register (GST_NVDEC_CUDA_GRAPHICS_RESOURCES_META_API_TYPE,
+        "GstNvDecCudaGraphicsResourcesMeta",
+        sizeof (GstNvDecCudaGraphicsResourcesMeta),
+        gst_nvdec_cuda_graphics_resources_meta_init,
+        gst_nvdec_cuda_graphics_resources_meta_free,
+        gst_nvdec_cuda_graphics_resources_meta_transform);
+    g_once_init_leave ((GstMetaInfo **) & meta_info, (GstMetaInfo *) mi);
+  }
+
+  return meta_info;
+}
+
+GstNvDecCudaGraphicsResourcesMeta *
+gst_buffer_add_nvdec_cuda_graphics_resources_meta (GstBuffer * buffer,
+    GstNvDecCudaContext * cuda_context)
+{
+  GstNvDecCudaGraphicsResourcesMeta *meta;
+  GstMemory *mem;
+
+  g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
+  g_return_val_if_fail (gst_buffer_n_memory (buffer) > 0, NULL);
+
+  mem = gst_buffer_get_memory (buffer, 0);
+  if (!gst_is_gl_memory (mem)) {
+    GST_WARNING ("memory is not GL memory");
+    gst_memory_unref (mem);
+    return NULL;
+  }
+
+  meta = (GstNvDecCudaGraphicsResourcesMeta *) gst_buffer_add_meta (buffer,
+      GST_NVDEC_CUDA_GRAPHICS_RESOURCES_META_INFO, NULL);
+  meta->cuda_context = g_object_ref (cuda_context);
+  gst_gl_context_thread_add (GST_GL_BASE_MEMORY_CAST (mem)->context,
+      (GstGLContextThreadFunc) add_cgr_meta, buffer);
+  gst_memory_unref (mem);
+
+  return meta;
+}
+
+static gboolean gst_nvdec_start (GstVideoDecoder * decoder);
+static gboolean gst_nvdec_stop (GstVideoDecoder * decoder);
+static gboolean gst_nvdec_set_format (GstVideoDecoder * decoder,
+    GstVideoCodecState * state);
+static GstFlowReturn gst_nvdec_handle_frame (GstVideoDecoder * decoder,
+    GstVideoCodecFrame * frame);
+static gboolean gst_nvdec_decide_allocation (GstVideoDecoder * decoder,
+    GstQuery * query);
+static void gst_nvdec_set_context (GstElement * element, GstContext * context);
+static gboolean gst_nvdec_src_query (GstVideoDecoder * decoder,
+    GstQuery * query);
+
+static GstStaticPadTemplate gst_nvdec_sink_template =
+    GST_STATIC_PAD_TEMPLATE (GST_VIDEO_DECODER_SINK_NAME,
+    GST_PAD_SINK, GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("video/x-h264, stream-format=byte-stream, alignment=au; "
+        "video/x-h265, stream-format=byte-stream, alignment=au; "
+        "video/mpeg, mpegversion={ 1, 2, 4 }, systemstream=false; "
+        "image/jpeg")
+    );
+
+static GstStaticPadTemplate gst_nvdec_src_template =
+GST_STATIC_PAD_TEMPLATE (GST_VIDEO_DECODER_SRC_NAME,
+    GST_PAD_SRC, GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
+        (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "NV12") ", texture-target=2D")
+    );
+
+G_DEFINE_TYPE_WITH_CODE (GstNvDec, gst_nvdec, GST_TYPE_VIDEO_DECODER,
+    GST_DEBUG_CATEGORY_INIT (gst_nvdec_debug_category, "nvdec", 0,
+        "Debug category for the nvdec element"));
+
+static void
+gst_nvdec_class_init (GstNvDecClass * klass)
+{
+  GstVideoDecoderClass *video_decoder_class = GST_VIDEO_DECODER_CLASS (klass);
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+  gst_element_class_add_static_pad_template (element_class,
+      &gst_nvdec_sink_template);
+  gst_element_class_add_static_pad_template (element_class,
+      &gst_nvdec_src_template);
+
+  gst_element_class_set_static_metadata (element_class, "NVDEC video decoder",
+      "Decoder/Video", "NVDEC video decoder",
+      "Ericsson AB, http://www.ericsson.com");
+
+  video_decoder_class->start = GST_DEBUG_FUNCPTR (gst_nvdec_start);
+  video_decoder_class->stop = GST_DEBUG_FUNCPTR (gst_nvdec_stop);
+  video_decoder_class->set_format = GST_DEBUG_FUNCPTR (gst_nvdec_set_format);
+  video_decoder_class->handle_frame =
+      GST_DEBUG_FUNCPTR (gst_nvdec_handle_frame);
+  video_decoder_class->decide_allocation =
+      GST_DEBUG_FUNCPTR (gst_nvdec_decide_allocation);
+  video_decoder_class->src_query = GST_DEBUG_FUNCPTR (gst_nvdec_src_query);
+
+  element_class->set_context = GST_DEBUG_FUNCPTR (gst_nvdec_set_context);
+}
+
+static void
+gst_nvdec_init (GstNvDec * nvdec)
+{
+  gst_video_decoder_set_packetized (GST_VIDEO_DECODER (nvdec), TRUE);
+  gst_video_decoder_set_needs_format (GST_VIDEO_DECODER (nvdec), TRUE);
+}
+
+static gboolean
+parser_sequence_callback (GstNvDec * nvdec, CUVIDEOFORMAT * format)
+{
+  GstNvDecQueueItem *item;
+  guint width, height;
+  CUVIDDECODECREATEINFO create_info = { 0, };
+  gboolean ret = TRUE;
+
+  width = format->display_area.right - format->display_area.left;
+  height = format->display_area.bottom - format->display_area.top;
+  GST_DEBUG_OBJECT (nvdec, "width: %u, height: %u", width, height);
+
+  if (!nvdec->decoder || (nvdec->width != width || nvdec->height != height)) {
+    if (!cuda_OK (cuvidCtxLock (nvdec->cuda_context->lock, 0))) {
+      GST_ERROR_OBJECT (nvdec, "failed to lock CUDA context");
+      return FALSE;
+    }
+
+    if (nvdec->decoder) {
+      GST_DEBUG_OBJECT (nvdec, "destroying decoder");
+      if (!cuda_OK (cuvidDestroyDecoder (nvdec->decoder))) {
+        GST_ERROR_OBJECT (nvdec, "failed to destroy decoder");
+        ret = FALSE;
+      } else
+        nvdec->decoder = NULL;
+    }
+
+    GST_DEBUG_OBJECT (nvdec, "creating decoder");
+    create_info.ulWidth = width;
+    create_info.ulHeight = height;
+    create_info.ulNumDecodeSurfaces = 20;
+    create_info.CodecType = format->codec;
+    create_info.ChromaFormat = format->chroma_format;
+    create_info.ulCreationFlags = cudaVideoCreate_Default;
+    create_info.display_area.left = format->display_area.left;
+    create_info.display_area.top = format->display_area.top;
+    create_info.display_area.right = format->display_area.right;
+    create_info.display_area.bottom = format->display_area.bottom;
+    create_info.OutputFormat = cudaVideoSurfaceFormat_NV12;
+    create_info.DeinterlaceMode = cudaVideoDeinterlaceMode_Weave;
+    create_info.ulTargetWidth = width;
+    create_info.ulTargetHeight = height;
+    create_info.ulNumOutputSurfaces = 1;
+    create_info.vidLock = nvdec->cuda_context->lock;
+    create_info.target_rect.left = 0;
+    create_info.target_rect.top = 0;
+    create_info.target_rect.right = width;
+    create_info.target_rect.bottom = height;
+
+    if (nvdec->decoder
+        || !cuda_OK (cuvidCreateDecoder (&nvdec->decoder, &create_info))) {
+      GST_ERROR_OBJECT (nvdec, "failed to create decoder");
+      ret = FALSE;
+    }
+
+    if (!cuda_OK (cuvidCtxUnlock (nvdec->cuda_context->lock, 0))) {
+      GST_ERROR_OBJECT (nvdec, "failed to unlock CUDA context");
+      ret = FALSE;
+    }
+  }
+
+  item = g_slice_new (GstNvDecQueueItem);
+  item->type = GST_NVDEC_QUEUE_ITEM_TYPE_SEQUENCE;
+  item->data = g_memdup (format, sizeof (CUVIDEOFORMAT));
+  g_async_queue_push (nvdec->decode_queue, item);
+
+  return ret;
+}
+
+static gboolean
+parser_decode_callback (GstNvDec * nvdec, CUVIDPICPARAMS * params)
+{
+  GstNvDecQueueItem *item;
+
+  GST_LOG_OBJECT (nvdec, "picture index: %u", params->CurrPicIdx);
+
+  if (!cuda_OK (cuvidCtxLock (nvdec->cuda_context->lock, 0)))
+    GST_WARNING_OBJECT (nvdec, "failed to lock CUDA context");
+
+  if (!cuda_OK (cuvidDecodePicture (nvdec->decoder, params)))
+    GST_WARNING_OBJECT (nvdec, "failed to decode picture");
+
+  if (!cuda_OK (cuvidCtxUnlock (nvdec->cuda_context->lock, 0)))
+    GST_WARNING_OBJECT (nvdec, "failed to unlock CUDA context");
+
+  item = g_slice_new (GstNvDecQueueItem);
+  item->type = GST_NVDEC_QUEUE_ITEM_TYPE_DECODE;
+  item->data = g_memdup (params, sizeof (CUVIDPICPARAMS));
+  ((CUVIDPICPARAMS *) item->data)->pBitstreamData = NULL;
+  ((CUVIDPICPARAMS *) item->data)->pSliceDataOffsets = NULL;
+  g_async_queue_push (nvdec->decode_queue, item);
+
+  return TRUE;
+}
+
+static gboolean
+parser_display_callback (GstNvDec * nvdec, CUVIDPARSERDISPINFO * dispinfo)
+{
+  GstNvDecQueueItem *item;
+
+  GST_LOG_OBJECT (nvdec, "picture index: %u", dispinfo->picture_index);
+
+  item = g_slice_new (GstNvDecQueueItem);
+  item->type = GST_NVDEC_QUEUE_ITEM_TYPE_DISPLAY;
+  item->data = g_memdup (dispinfo, sizeof (CUVIDPARSERDISPINFO));
+  g_async_queue_push (nvdec->decode_queue, item);
+
+  return TRUE;
+}
+
+static gboolean
+gst_nvdec_start (GstVideoDecoder * decoder)
+{
+  GstNvDec *nvdec = GST_NVDEC (decoder);
+
+  GST_DEBUG_OBJECT (nvdec, "creating CUDA context");
+  nvdec->cuda_context = g_object_new (gst_nvdec_cuda_context_get_type (), NULL);
+  nvdec->decode_queue = g_async_queue_new ();
+
+  if (!nvdec->cuda_context->context || !nvdec->cuda_context->lock) {
+    GST_ERROR_OBJECT (nvdec, "failed to create CUDA context or lock");
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+maybe_destroy_decoder_and_parser (GstNvDec * nvdec)
+{
+  gboolean ret = TRUE;
+
+  if (!cuda_OK (cuvidCtxLock (nvdec->cuda_context->lock, 0))) {
+    GST_ERROR_OBJECT (nvdec, "failed to lock CUDA context");
+    return FALSE;
+  }
+
+  if (nvdec->decoder) {
+    GST_DEBUG_OBJECT (nvdec, "destroying decoder");
+    ret = cuda_OK (cuvidDestroyDecoder (nvdec->decoder));
+    if (ret)
+      nvdec->decoder = NULL;
+    else
+      GST_ERROR_OBJECT (nvdec, "failed to destroy decoder");
+  }
+
+  if (!cuda_OK (cuvidCtxUnlock (nvdec->cuda_context->lock, 0))) {
+    GST_ERROR_OBJECT (nvdec, "failed to unlock CUDA context");
+    return FALSE;
+  }
+
+  if (nvdec->parser) {
+    GST_DEBUG_OBJECT (nvdec, "destroying parser");
+    if (!cuda_OK (cuvidDestroyVideoParser (nvdec->parser))) {
+      GST_ERROR_OBJECT (nvdec, "failed to destroy parser");
+      return FALSE;
+    }
+    nvdec->parser = NULL;
+  }
+
+  return ret;
+}
+
+static gboolean
+gst_nvdec_stop (GstVideoDecoder * decoder)
+{
+  GstNvDec *nvdec = GST_NVDEC (decoder);
+  GstNvDecQueueItem *item;
+
+  GST_DEBUG_OBJECT (nvdec, "stop");
+
+  if (!maybe_destroy_decoder_and_parser (nvdec))
+    return FALSE;
+
+  if (nvdec->cuda_context) {
+    g_object_unref (nvdec->cuda_context);
+    nvdec->cuda_context = NULL;
+  }
+
+  if (nvdec->gl_context) {
+    gst_object_unref (nvdec->gl_context);
+    nvdec->gl_context = NULL;
+  }
+
+  if (nvdec->other_gl_context) {
+    gst_object_unref (nvdec->other_gl_context);
+    nvdec->other_gl_context = NULL;
+  }
+
+  if (nvdec->gl_display) {
+    gst_object_unref (nvdec->gl_display);
+    nvdec->gl_display = NULL;
+  }
+
+  if (nvdec->input_state) {
+    gst_video_codec_state_unref (nvdec->input_state);
+    nvdec->input_state = NULL;
+  }
+
+  if (nvdec->decode_queue) {
+    if (g_async_queue_length (nvdec->decode_queue) > 0) {
+      GST_INFO_OBJECT (nvdec, "decode queue not empty");
+
+      while ((item = g_async_queue_try_pop (nvdec->decode_queue))) {
+        g_free (item->data);
+        g_slice_free (GstNvDecQueueItem, item);
+      }
+    }
+    g_async_queue_unref (nvdec->decode_queue);
+    nvdec->decode_queue = NULL;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+gst_nvdec_set_format (GstVideoDecoder * decoder, GstVideoCodecState * state)
+{
+  GstNvDec *nvdec = GST_NVDEC (decoder);
+  GstStructure *s;
+  const gchar *caps_name;
+  gint mpegversion = 0;
+  CUVIDPARSERPARAMS parser_params = { 0, };
+
+  GST_DEBUG_OBJECT (nvdec, "set format");
+
+  if (nvdec->input_state)
+    gst_video_codec_state_unref (nvdec->input_state);
+
+  nvdec->input_state = gst_video_codec_state_ref (state);
+
+  if (!maybe_destroy_decoder_and_parser (nvdec))
+    return FALSE;
+
+  s = gst_caps_get_structure (state->caps, 0);
+  caps_name = gst_structure_get_name (s);
+  GST_DEBUG_OBJECT (nvdec, "codec is %s", caps_name);
+
+  if (!g_strcmp0 (caps_name, "video/mpeg")) {
+    if (gst_structure_get_int (s, "mpegversion", &mpegversion)) {
+      switch (mpegversion) {
+        case 1:
+          parser_params.CodecType = cudaVideoCodec_MPEG1;
+          break;
+        case 2:
+          parser_params.CodecType = cudaVideoCodec_MPEG2;
+          break;
+        case 4:
+          parser_params.CodecType = cudaVideoCodec_MPEG4;
+          break;
+      }
+    }
+    if (!mpegversion) {
+      GST_ERROR_OBJECT (nvdec, "could not get MPEG version");
+      return FALSE;
+    }
+  } else if (!g_strcmp0 (caps_name, "video/x-h264")) {
+    parser_params.CodecType = cudaVideoCodec_H264;
+  } else if (!g_strcmp0 (caps_name, "image/jpeg")) {
+    parser_params.CodecType = cudaVideoCodec_JPEG;
+  } else if (!g_strcmp0 (caps_name, "video/x-h265")) {
+    parser_params.CodecType = cudaVideoCodec_HEVC;
+  } else {
+    GST_ERROR_OBJECT (nvdec, "failed to determine codec type");
+    return FALSE;
+  }
+
+  parser_params.ulMaxNumDecodeSurfaces = 20;
+  parser_params.ulErrorThreshold = 100;
+  parser_params.ulMaxDisplayDelay = 0;
+  parser_params.ulClockRate = GST_SECOND;
+  parser_params.pUserData = nvdec;
+  parser_params.pfnSequenceCallback =
+      (PFNVIDSEQUENCECALLBACK) parser_sequence_callback;
+  parser_params.pfnDecodePicture =
+      (PFNVIDDECODECALLBACK) parser_decode_callback;
+  parser_params.pfnDisplayPicture =
+      (PFNVIDDISPLAYCALLBACK) parser_display_callback;
+
+  GST_DEBUG_OBJECT (nvdec, "creating parser");
+  if (!cuda_OK (cuvidCreateVideoParser (&nvdec->parser, &parser_params))) {
+    GST_ERROR_OBJECT (nvdec, "failed to create parser");
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static void
+copy_video_frame_to_gl_textures (GstGLContext * context, gpointer * args)
+{
+  GstNvDec *nvdec = GST_NVDEC (args[0]);
+  CUVIDPARSERDISPINFO *dispinfo = (CUVIDPARSERDISPINFO *) args[1];
+  GstNvDecCudaGraphicsResourcesMeta *meta =
+      (GstNvDecCudaGraphicsResourcesMeta *) args[2];
+  CUVIDPROCPARAMS proc_params = { 0, };
+  CUdeviceptr dptr;
+  CUarray array;
+  guint pitch, i;
+  CUDA_MEMCPY2D mcpy2d = { 0, };
+
+  GST_LOG_OBJECT (nvdec, "picture index: %u", dispinfo->picture_index);
+
+  proc_params.progressive_frame = dispinfo->progressive_frame;
+  proc_params.top_field_first = dispinfo->top_field_first;
+  proc_params.unpaired_field = dispinfo->repeat_first_field == -1;
+
+  if (!cuda_OK (cuvidCtxLock (nvdec->cuda_context->lock, 0))) {
+    GST_WARNING_OBJECT (nvdec, "failed to lock CUDA context");
+    return;
+  }
+
+  if (!cuda_OK (cuvidMapVideoFrame (nvdec->decoder, dispinfo->picture_index,
+              &dptr, &pitch, &proc_params))) {
+    GST_WARNING_OBJECT (nvdec, "failed to map CUDA video frame");
+    goto unlock_cuda_context;
+  }
+
+  if (!cuda_OK (cuGraphicsMapResources (meta->num_resources, meta->resources,
+              NULL))) {
+    GST_WARNING_OBJECT (nvdec, "failed to map CUDA resources");
+    goto unmap_video_frame;
+  }
+
+  mcpy2d.srcMemoryType = CU_MEMORYTYPE_DEVICE;
+  mcpy2d.srcPitch = pitch;
+  mcpy2d.dstMemoryType = CU_MEMORYTYPE_ARRAY;
+  mcpy2d.dstPitch = nvdec->width;
+  mcpy2d.WidthInBytes = nvdec->width;
+
+  for (i = 0; i < meta->num_resources; i++) {
+    if (!cuda_OK (cuGraphicsSubResourceGetMappedArray (&array,
+                meta->resources[i], 0, 0))) {
+      GST_WARNING_OBJECT (nvdec, "failed to map CUDA array");
+      break;
+    }
+
+    mcpy2d.srcDevice = dptr + (i * pitch * nvdec->height);
+    mcpy2d.dstArray = array;
+    mcpy2d.Height = nvdec->height / (i + 1);
+
+    if (!cuda_OK (cuMemcpy2D (&mcpy2d)))
+      GST_WARNING_OBJECT (nvdec, "memcpy to mapped array failed");
+  }
+
+  if (!cuda_OK (cuGraphicsUnmapResources (meta->num_resources, meta->resources,
+              NULL)))
+    GST_WARNING_OBJECT (nvdec, "failed to unmap CUDA resources");
+
+unmap_video_frame:
+  if (!cuda_OK (cuvidUnmapVideoFrame (nvdec->decoder, dptr)))
+    GST_WARNING_OBJECT (nvdec, "failed to unmap CUDA video frame");
+
+unlock_cuda_context:
+  if (!cuda_OK (cuvidCtxUnlock (nvdec->cuda_context->lock, 0)))
+    GST_WARNING_OBJECT (nvdec, "failed to unlock CUDA context");
+}
+
+static GstFlowReturn
+handle_pending_frames (GstNvDec * nvdec)
+{
+  GstVideoDecoder *decoder = GST_VIDEO_DECODER (nvdec);
+  GList *pending_frames, *list, *tmp;
+  GstVideoCodecFrame *pending_frame;
+  guint frame_number;
+  GstClockTime latency = 0;
+  GstNvDecQueueItem *item;
+  CUVIDEOFORMAT *format;
+  GstVideoCodecState *state;
+  guint width, height, fps_n, fps_d, i;
+  CUVIDPICPARAMS *decode_params;
+  CUVIDPARSERDISPINFO *dispinfo;
+  GstNvDecCudaGraphicsResourcesMeta *meta;
+  gpointer args[3];
+  GstMemory *mem;
+  GstFlowReturn ret = GST_FLOW_OK;
+
+  /* find the oldest unused, unfinished frame */
+  pending_frames = list = gst_video_decoder_get_frames (decoder);
+  for (; pending_frames; pending_frames = pending_frames->next) {
+    pending_frame = pending_frames->data;
+    frame_number =
+        GPOINTER_TO_UINT (gst_video_codec_frame_get_user_data (pending_frame));
+    if (!frame_number)
+      break;
+    latency += pending_frame->duration;
+  }
+
+  while (ret == GST_FLOW_OK && pending_frames
+      && (item =
+          (GstNvDecQueueItem *) g_async_queue_try_pop (nvdec->decode_queue))) {
+    switch (item->type) {
+      case GST_NVDEC_QUEUE_ITEM_TYPE_SEQUENCE:
+        if (!nvdec->decoder) {
+          GST_ERROR_OBJECT (nvdec, "no decoder");
+          ret = GST_FLOW_ERROR;
+          break;
+        }
+
+        format = (CUVIDEOFORMAT *) item->data;
+        width = format->display_area.right - format->display_area.left;
+        height = format->display_area.bottom - format->display_area.top;
+        fps_n = format->frame_rate.numerator;
+        fps_d = MAX (1, format->frame_rate.denominator);
+
+        if (!gst_pad_has_current_caps (GST_VIDEO_DECODER_SRC_PAD (decoder))
+            || width != nvdec->width || height != nvdec->height
+            || fps_n != nvdec->fps_n || fps_d != nvdec->fps_d) {
+          nvdec->width = width;
+          nvdec->height = height;
+          nvdec->fps_n = fps_n;
+          nvdec->fps_d = fps_d;
+
+          state = gst_video_decoder_set_output_state (decoder,
+              GST_VIDEO_FORMAT_NV12, nvdec->width, nvdec->height,
+              nvdec->input_state);
+          state->caps = gst_caps_new_simple ("video/x-raw",
+              "format", G_TYPE_STRING, "NV12",
+              "width", G_TYPE_INT, nvdec->width,
+              "height", G_TYPE_INT, nvdec->height,
+              "framerate", GST_TYPE_FRACTION, nvdec->fps_n, nvdec->fps_d,
+              "interlace-mode", G_TYPE_STRING, format->progressive_sequence
+              ? "progressive" : "interleaved",
+              "texture-target", G_TYPE_STRING, "2D", NULL);
+          gst_caps_set_features (state->caps, 0,
+              gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, NULL));
+          gst_video_codec_state_unref (state);
+
+          if (!gst_video_decoder_negotiate (decoder)) {
+            GST_WARNING_OBJECT (nvdec, "failed to negotiate with downstream");
+            ret = GST_FLOW_NOT_NEGOTIATED;
+            break;
+          }
+        }
+
+        break;
+
+      case GST_NVDEC_QUEUE_ITEM_TYPE_DECODE:
+        decode_params = (CUVIDPICPARAMS *) item->data;
+        pending_frame = pending_frames->data;
+        frame_number = decode_params->CurrPicIdx + 1;
+        gst_video_codec_frame_set_user_data (pending_frame,
+            GUINT_TO_POINTER (frame_number), NULL);
+
+        if (decode_params->intra_pic_flag)
+          GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (pending_frame);
+
+        if (!GST_CLOCK_TIME_IS_VALID (pending_frame->duration)) {
+          pending_frame->duration =
+              nvdec->fps_n ? GST_SECOND * nvdec->fps_d / nvdec->fps_n : 0;
+        }
+        latency += pending_frame->duration;
+
+        pending_frames = pending_frames->next;
+
+        break;
+
+      case GST_NVDEC_QUEUE_ITEM_TYPE_DISPLAY:
+        dispinfo = (CUVIDPARSERDISPINFO *) item->data;
+        for (pending_frame = NULL, tmp = list; !pending_frame && tmp;
+            tmp = tmp->next) {
+          frame_number =
+              GPOINTER_TO_UINT (gst_video_codec_frame_get_user_data
+              (tmp->data));
+          if (frame_number == dispinfo->picture_index + 1)
+            pending_frame = tmp->data;
+        }
+        if (!pending_frame) {
+          GST_INFO_OBJECT (nvdec, "no frame with number %u",
+              dispinfo->picture_index + 1);
+          break;
+        }
+
+        if (dispinfo->timestamp != pending_frame->pts) {
+          GST_INFO_OBJECT (nvdec,
+              "timestamp mismatch, diff: %" GST_STIME_FORMAT,
+              GST_STIME_ARGS (GST_CLOCK_DIFF (dispinfo->timestamp,
+                      pending_frame->pts)));
+          pending_frame->pts = dispinfo->timestamp;
+        }
+
+        if (latency > nvdec->min_latency) {
+          nvdec->min_latency = latency;
+          gst_video_decoder_set_latency (decoder, nvdec->min_latency,
+              nvdec->min_latency);
+          GST_DEBUG_OBJECT (nvdec, "latency: %" GST_TIME_FORMAT,
+              GST_TIME_ARGS (latency));
+        }
+        latency -= pending_frame->duration;
+
+        ret = gst_video_decoder_allocate_output_frame (decoder, pending_frame);
+        if (ret != GST_FLOW_OK) {
+          GST_WARNING_OBJECT (nvdec, "failed to allocate output frame");
+          break;
+        }
+
+        meta = gst_buffer_get_nvdec_cuda_graphics_resources_meta
+            (pending_frame->output_buffer);
+        if (!meta) {
+          meta = gst_buffer_add_nvdec_cuda_graphics_resources_meta
+              (pending_frame->output_buffer, nvdec->cuda_context);
+          if (!meta) {
+            GST_WARNING_OBJECT (nvdec,
+                "failed to add CUDA graphics resources meta");
+            break;
+          }
+          GST_META_FLAG_SET (meta, GST_META_FLAG_POOLED);
+        }
+
+        args[0] = nvdec;
+        args[1] = dispinfo;
+        args[2] = meta;
+        gst_gl_context_thread_add (nvdec->gl_context,
+            (GstGLContextThreadFunc) copy_video_frame_to_gl_textures, args);
+
+        for (i = gst_buffer_n_memory (pending_frame->output_buffer); i;) {
+          mem = gst_buffer_get_memory (pending_frame->output_buffer, --i);
+          GST_MINI_OBJECT_FLAG_SET (mem,
+              GST_GL_BASE_MEMORY_TRANSFER_NEED_DOWNLOAD);
+          gst_memory_unref (mem);
+        }
+
+        if (!dispinfo->progressive_frame) {
+          GST_BUFFER_FLAG_SET (pending_frame->output_buffer,
+              GST_VIDEO_BUFFER_FLAG_INTERLACED);
+
+          if (dispinfo->top_field_first) {
+            GST_BUFFER_FLAG_SET (pending_frame->output_buffer,
+                GST_VIDEO_BUFFER_FLAG_TFF);
+          }
+          if (dispinfo->repeat_first_field == -1) {
+            GST_BUFFER_FLAG_SET (pending_frame->output_buffer,
+                GST_VIDEO_BUFFER_FLAG_ONEFIELD);
+          } else {
+            GST_BUFFER_FLAG_SET (pending_frame->output_buffer,
+                GST_VIDEO_BUFFER_FLAG_RFF);
+          }
+        }
+
+        list = g_list_remove (list, pending_frame);
+        ret = gst_video_decoder_finish_frame (decoder, pending_frame);
+        if (ret != GST_FLOW_OK)
+          GST_INFO_OBJECT (nvdec, "failed to finish frame");
+
+        break;
+
+      default:
+        g_assert_not_reached ();
+    }
+
+    g_free (item->data);
+    g_slice_free (GstNvDecQueueItem, item);
+  }
+
+  g_list_free_full (list, (GDestroyNotify) gst_video_codec_frame_unref);
+
+  return ret;
+}
+
+static GstFlowReturn
+gst_nvdec_handle_frame (GstVideoDecoder * decoder, GstVideoCodecFrame * frame)
+{
+  GstNvDec *nvdec = GST_NVDEC (decoder);
+  GstMapInfo map_info = GST_MAP_INFO_INIT;
+  CUVIDSOURCEDATAPACKET packet = { 0, };
+
+  GST_LOG_OBJECT (nvdec, "handle frame");
+
+  gst_video_codec_frame_set_user_data (frame, GUINT_TO_POINTER (0), NULL);
+
+  if (!gst_buffer_map (frame->input_buffer, &map_info, GST_MAP_READ)) {
+    GST_ERROR_OBJECT (nvdec, "failed to map input buffer");
+    gst_video_codec_frame_unref (frame);
+    return GST_FLOW_ERROR;
+  }
+
+  packet.payload_size = (gulong) map_info.size;
+  packet.payload = map_info.data;
+  packet.timestamp = frame->pts;
+  packet.flags = CUVID_PKT_TIMESTAMP;
+
+  if (GST_BUFFER_IS_DISCONT (frame->input_buffer))
+    packet.flags &= CUVID_PKT_DISCONTINUITY;
+
+  if (!cuda_OK (cuvidParseVideoData (nvdec->parser, &packet)))
+    GST_WARNING_OBJECT (nvdec, "parser failed");
+
+  gst_buffer_unmap (frame->input_buffer, &map_info);
+  gst_video_codec_frame_unref (frame);
+
+  return handle_pending_frames (nvdec);
+}
+
+static gboolean
+gst_nvdec_decide_allocation (GstVideoDecoder * decoder, GstQuery * query)
+{
+  GstNvDec *nvdec = GST_NVDEC (decoder);
+  GstCaps *outcaps;
+  GstBufferPool *pool = NULL;
+  guint n, size, min, max;
+  GstVideoInfo vinfo = { 0, };
+  GstStructure *config;
+
+  GST_DEBUG_OBJECT (nvdec, "decide allocation");
+
+  if (!gst_gl_ensure_element_data (nvdec, &nvdec->gl_display,
+          &nvdec->other_gl_context)) {
+    GST_ERROR_OBJECT (nvdec, "failed to ensure OpenGL display");
+    return FALSE;
+  }
+
+  if (!gst_gl_query_local_gl_context (GST_ELEMENT (decoder), GST_PAD_SRC,
+          &nvdec->gl_context)) {
+    GST_INFO_OBJECT (nvdec, "failed to query local OpenGL context");
+    if (nvdec->gl_context)
+      gst_object_unref (nvdec->gl_context);
+    nvdec->gl_context =
+        gst_gl_display_get_gl_context_for_thread (nvdec->gl_display, NULL);
+    if (!nvdec->gl_context
+        || !gst_gl_display_add_context (nvdec->gl_display, nvdec->gl_context)) {
+      if (nvdec->gl_context)
+        gst_object_unref (nvdec->gl_context);
+      if (!gst_gl_display_create_context (nvdec->gl_display,
+              nvdec->other_gl_context, &nvdec->gl_context, NULL)) {
+        GST_ERROR_OBJECT (nvdec, "failed to create OpenGL context");
+        return FALSE;
+      }
+      if (!gst_gl_display_add_context (nvdec->gl_display, nvdec->gl_context)) {
+        GST_ERROR_OBJECT (nvdec,
+            "failed to add the OpenGL context to the display");
+        return FALSE;
+      }
+    }
+  }
+
+  gst_query_parse_allocation (query, &outcaps, NULL);
+  n = gst_query_get_n_allocation_pools (query);
+  if (n > 0) {
+    gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
+    if (!GST_IS_GL_BUFFER_POOL (pool)) {
+      gst_object_unref (pool);
+      pool = NULL;
+    }
+  }
+
+  if (!pool) {
+    pool = gst_gl_buffer_pool_new (nvdec->gl_context);
+
+    if (outcaps)
+      gst_video_info_from_caps (&vinfo, outcaps);
+    size = (guint) vinfo.size;
+    min = max = 0;
+  }
+
+  config = gst_buffer_pool_get_config (pool);
+  gst_buffer_pool_config_set_params (config, outcaps, size, min, max);
+  gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
+  gst_buffer_pool_set_config (pool, config);
+  if (n > 0)
+    gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max);
+  else
+    gst_query_add_allocation_pool (query, pool, size, min, max);
+  gst_object_unref (pool);
+
+  return GST_VIDEO_DECODER_CLASS (gst_nvdec_parent_class)->decide_allocation
+      (decoder, query);
+}
+
+static gboolean
+gst_nvdec_src_query (GstVideoDecoder * decoder, GstQuery * query)
+{
+  GstNvDec *nvdec = GST_NVDEC (decoder);
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_CONTEXT:
+      if (gst_gl_handle_context_query (GST_ELEMENT (decoder), query,
+              nvdec->gl_display, nvdec->gl_context, nvdec->other_gl_context))
+        return TRUE;
+      break;
+    default:
+      break;
+  }
+
+  return GST_VIDEO_DECODER_CLASS (gst_nvdec_parent_class)->src_query (decoder,
+      query);
+}
+
+static void
+gst_nvdec_set_context (GstElement * element, GstContext * context)
+{
+  GstNvDec *nvdec = GST_NVDEC (element);
+  GST_DEBUG_OBJECT (nvdec, "set context");
+
+  gst_gl_handle_set_context (element, context, &nvdec->gl_display,
+      &nvdec->other_gl_context);
+
+  GST_ELEMENT_CLASS (gst_nvdec_parent_class)->set_context (element, context);
+}
diff --git a/sys/nvdec/gstnvdec.h b/sys/nvdec/gstnvdec.h
new file mode 100644 (file)
index 0000000..7bc6e81
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __GST_NVDEC_H__
+#define __GST_NVDEC_H__
+
+#include <gst/gl/gl.h>
+#include <nvcuvid.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GstNvDecCudaContext GstNvDecCudaContext;
+typedef struct _GstNvDecCudaContextClass GstNvDecCudaContextClass;
+
+struct _GstNvDecCudaContext
+{
+  GObject parent;
+
+  CUcontext context;
+  CUvideoctxlock lock;
+};
+
+struct _GstNvDecCudaContextClass
+{
+  GObjectClass parent_class;
+};
+
+GType gst_nvdec_cuda_context_get_type (void);
+
+
+#define GST_TYPE_NVDEC          (gst_nvdec_get_type())
+#define GST_NVDEC(obj)          (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_NVDEC, GstNvDec))
+#define GST_NVDEC_CLASS(klass)  (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_NVDEC, GstNvDecClass))
+#define GST_IS_NVDEC(obj)       (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_NVDEC))
+#define GST_IS_NVDEC_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_NVDEC))
+
+typedef struct _GstNvDec GstNvDec;
+typedef struct _GstNvDecClass GstNvDecClass;
+
+struct _GstNvDec
+{
+  GstVideoDecoder parent;
+
+  GstGLDisplay *gl_display;
+  GstGLContext *gl_context;
+  GstGLContext *other_gl_context;
+
+  GstNvDecCudaContext *cuda_context;
+  CUvideoparser parser;
+  CUvideodecoder decoder;
+  GAsyncQueue *decode_queue;
+
+  guint width;
+  guint height;
+  guint fps_n;
+  guint fps_d;
+  GstClockTime min_latency;
+  GstVideoCodecState *input_state;
+};
+
+struct _GstNvDecClass
+{
+  GstVideoDecoderClass parent_class;
+};
+
+GType gst_nvdec_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_NVDEC_H__ */
diff --git a/sys/nvdec/plugin.c b/sys/nvdec/plugin.c
new file mode 100644 (file)
index 0000000..6a08149
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 Ericsson AB. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstnvdec.h"
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  return gst_element_register (plugin, "nvdec", GST_RANK_PRIMARY,
+      GST_TYPE_NVDEC);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, nvdec,
+    "GStreamer NVDEC plugin", plugin_init, VERSION, "BSD",
+    GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)