nvcodec: Add H264 stateless codec implementation
authorSeungha Yang <seungha@centricular.com>
Thu, 16 Apr 2020 14:27:31 +0000 (23:27 +0900)
committerGStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Fri, 24 Apr 2020 09:23:10 +0000 (09:23 +0000)
Introduce GstH264Decoder based Nvidia H.264 decoder element.
Similar the element factory name of to v4l2 stateless codec,
this element can be configured with factory name "gstnvh264sldec".
Note that "sl" in the name stands for "stateless"

For now, existing nvh264dec covers more profile and formats
(e.g., interlaced stream) than this implementation.
However, this implementation allows us to control lower level
parameters such as decoded picture buffer management and therefore
we can get a chance to improve performance in terms of latency.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1198>

sys/nvcodec/gstnvdec.c
sys/nvcodec/gstnvdec.h
sys/nvcodec/gstnvdecoder.c [new file with mode: 0644]
sys/nvcodec/gstnvdecoder.h [new file with mode: 0644]
sys/nvcodec/gstnvh264dec.c [new file with mode: 0644]
sys/nvcodec/gstnvh264dec.h [new file with mode: 0644]
sys/nvcodec/meson.build
sys/nvcodec/plugin.c

index 2b455a5b6c08b939ddcbb2e0addc379acdc96e8d..dfad5a75fcbeb908c8600ad49414ce94df1e6328 100644 (file)
@@ -1514,9 +1514,6 @@ gst_nvdec_subclass_register (GstPlugin * plugin, GType type,
   cdata->codec_type = codec_type;
   cdata->codec = g_strdup (codec);
   cdata->cuda_device_id = device_id;
-  /* class data will be leaked if the element never gets instantiated */
-  GST_MINI_OBJECT_FLAG_SET (sink_caps, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED);
-  GST_MINI_OBJECT_FLAG_SET (src_caps, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED);
 
   g_type_query (type, &type_query);
   memset (&type_info, 0, sizeof (type_info));
@@ -1546,380 +1543,11 @@ gst_nvdec_subclass_register (GstPlugin * plugin, GType type,
   g_free (type_name);
 }
 
-typedef enum
-{
-  GST_NVDEC_FORMAT_FLAG_NONE = (1 << 0),
-  GST_NVDEC_FORMAT_FLAG_420_8BITS = (1 << 1),
-  GST_NVDEC_FORMAT_FLAG_420_10BITS = (1 << 2),
-  GST_NVDEC_FORMAT_FLAG_420_12BITS = (1 << 3),
-  GST_NVDEC_FORMAT_FLAG_444_8BITS = (1 << 4),
-  GST_NVDEC_FORMAT_FLAG_444_10BITS = (1 << 5),
-  GST_NVDEC_FORMAT_FLAG_444_12BITS = (1 << 6),
-} GstNvDecFormatFlags;
-
-static gboolean
-gst_nvdec_get_supported_codec_profiles (GValue * profiles,
-    cudaVideoCodec codec_type, GstNvDecFormatFlags flags)
-{
-  GValue val = G_VALUE_INIT;
-  gboolean ret = FALSE;
-
-  g_value_init (&val, G_TYPE_STRING);
-
-  switch (codec_type) {
-    case cudaVideoCodec_H264:
-      if ((flags & GST_NVDEC_FORMAT_FLAG_420_8BITS) ==
-          GST_NVDEC_FORMAT_FLAG_420_8BITS) {
-        g_value_set_static_string (&val, "constrained-baseline");
-        gst_value_list_append_value (profiles, &val);
-
-        g_value_set_static_string (&val, "baseline");
-        gst_value_list_append_value (profiles, &val);
-
-        g_value_set_static_string (&val, "main");
-        gst_value_list_append_value (profiles, &val);
-
-        g_value_set_static_string (&val, "high");
-        gst_value_list_append_value (profiles, &val);
-      }
-
-      /* NVDEC supports only 4:2:0 8bits h264 decoding.
-       * following conditions are for the future enhancement */
-      if ((flags & GST_NVDEC_FORMAT_FLAG_420_10BITS) ==
-          GST_NVDEC_FORMAT_FLAG_420_10BITS) {
-        g_value_set_static_string (&val, "high-10");
-        gst_value_list_append_value (profiles, &val);
-      }
-
-      if ((flags & GST_NVDEC_FORMAT_FLAG_420_12BITS) ==
-          GST_NVDEC_FORMAT_FLAG_420_12BITS ||
-          (flags & GST_NVDEC_FORMAT_FLAG_444_8BITS) ==
-          GST_NVDEC_FORMAT_FLAG_444_8BITS ||
-          (flags & GST_NVDEC_FORMAT_FLAG_444_10BITS) ==
-          GST_NVDEC_FORMAT_FLAG_444_10BITS ||
-          (flags & GST_NVDEC_FORMAT_FLAG_444_12BITS) ==
-          GST_NVDEC_FORMAT_FLAG_444_12BITS) {
-        g_value_set_static_string (&val, "high-4:4:4");
-        gst_value_list_append_value (profiles, &val);
-      }
-
-      ret = TRUE;
-      break;
-    case cudaVideoCodec_HEVC:
-      if ((flags & GST_NVDEC_FORMAT_FLAG_420_8BITS) ==
-          GST_NVDEC_FORMAT_FLAG_420_8BITS) {
-        g_value_set_static_string (&val, "main");
-        gst_value_list_append_value (profiles, &val);
-      }
-
-      if ((flags & GST_NVDEC_FORMAT_FLAG_420_10BITS) ==
-          GST_NVDEC_FORMAT_FLAG_420_10BITS) {
-        g_value_set_static_string (&val, "main-10");
-        gst_value_list_append_value (profiles, &val);
-      }
-
-      if ((flags & GST_NVDEC_FORMAT_FLAG_420_12BITS) ==
-          GST_NVDEC_FORMAT_FLAG_420_12BITS) {
-        g_value_set_static_string (&val, "main-12");
-        gst_value_list_append_value (profiles, &val);
-      }
-
-      if ((flags & GST_NVDEC_FORMAT_FLAG_444_8BITS) ==
-          GST_NVDEC_FORMAT_FLAG_444_8BITS) {
-        g_value_set_static_string (&val, "main-444");
-        gst_value_list_append_value (profiles, &val);
-      }
-
-      if ((flags & GST_NVDEC_FORMAT_FLAG_444_10BITS) ==
-          GST_NVDEC_FORMAT_FLAG_444_10BITS) {
-        g_value_set_static_string (&val, "main-444-10");
-        gst_value_list_append_value (profiles, &val);
-      }
-
-      if ((flags & GST_NVDEC_FORMAT_FLAG_444_12BITS) ==
-          GST_NVDEC_FORMAT_FLAG_444_12BITS) {
-        g_value_set_static_string (&val, "main-444-12");
-        gst_value_list_append_value (profiles, &val);
-      }
-
-      ret = TRUE;
-      break;
-    default:
-      break;
-  }
-
-  g_value_unset (&val);
-
-  return ret;
-}
-
-typedef struct
-{
-  guint idx;
-  cudaVideoChromaFormat format;
-} GstNvdecChromaMap;
-
-static void
-gst_nvdec_register (GstPlugin * plugin, GType type, cudaVideoCodec codec_type,
-    const gchar * codec, const gchar * sink_caps_string, guint rank,
-    gint device_idx, CUcontext cuda_ctx)
-{
-  {
-    CUresult cuda_ret;
-    gint max_width = 0, min_width = G_MAXINT;
-    gint max_height = 0, min_height = G_MAXINT;
-    GstCaps *sink_templ = NULL;
-    GstCaps *src_templ = NULL;
-    /* FIXME: support 12bits format */
-    guint bitdepth_minus8[3] = { 0, 2, 4 };
-    GstNvDecFormatFlags format_flags = 0;
-    gint c_idx, b_idx;
-    guint num_support = 0;
-    cudaVideoChromaFormat chroma_list[] = {
-#if 0
-      /* FIXME: support monochrome */
-      cudaVideoChromaFormat_Monochrome,
-      /* FIXME: Can our OpenGL support NV16 and its 10/12bits variant?? */
-      cudaVideoChromaFormat_422,
-#endif
-      cudaVideoChromaFormat_420,
-      cudaVideoChromaFormat_444,
-    };
-    GValue format_list = G_VALUE_INIT;
-    GValue format = G_VALUE_INIT;
-    GValue profile_list = G_VALUE_INIT;
-
-    g_value_init (&format_list, GST_TYPE_LIST);
-    g_value_init (&format, G_TYPE_STRING);
-    g_value_init (&profile_list, GST_TYPE_LIST);
-
-    if (CuCtxPushCurrent (cuda_ctx) != CUDA_SUCCESS)
-      goto done;
-
-    for (c_idx = 0; c_idx < G_N_ELEMENTS (chroma_list); c_idx++) {
-      for (b_idx = 0; b_idx < G_N_ELEMENTS (bitdepth_minus8); b_idx++) {
-        CUVIDDECODECAPS decoder_caps = { 0, };
-        GstNvDecFormatFlags cur_flag = 0;
-
-        decoder_caps.eCodecType = codec_type;
-        decoder_caps.eChromaFormat = chroma_list[c_idx];
-        decoder_caps.nBitDepthMinus8 = bitdepth_minus8[b_idx];
-
-        cuda_ret = CuvidGetDecoderCaps (&decoder_caps);
-        if (cuda_ret != CUDA_SUCCESS) {
-          GST_INFO ("could not query %s decoder capability, ret %d",
-              codec, cuda_ret);
-          continue;
-        } else if (!decoder_caps.bIsSupported) {
-          GST_LOG ("%s bit-depth %d with chroma format %d is not supported",
-              codec, bitdepth_minus8[b_idx] + 8, c_idx);
-          continue;
-        }
-
-        if (min_width > decoder_caps.nMinWidth)
-          min_width = decoder_caps.nMinWidth;
-        if (min_height > decoder_caps.nMinHeight)
-          min_height = decoder_caps.nMinHeight;
-        if (max_width < decoder_caps.nMaxWidth)
-          max_width = decoder_caps.nMaxWidth;
-        if (max_height < decoder_caps.nMaxHeight)
-          max_height = decoder_caps.nMaxHeight;
-
-        if (chroma_list[c_idx] == cudaVideoChromaFormat_420)
-          cur_flag = GST_NVDEC_FORMAT_FLAG_420_8BITS;
-        else
-          cur_flag = GST_NVDEC_FORMAT_FLAG_444_8BITS;
-
-        format_flags |= (cur_flag << (bitdepth_minus8[b_idx] / 2));
-
-        GST_INFO ("%s bit-depth %d with chroma format %d [%d - %d] x [%d - %d]",
-            codec, bitdepth_minus8[b_idx] + 8, c_idx, min_width, max_width,
-            min_height, max_height);
-
-        switch (chroma_list[c_idx]) {
-          case cudaVideoChromaFormat_420:
-            if (bitdepth_minus8[b_idx] == 0) {
-              g_value_set_string (&format, "NV12");
-            } else if (bitdepth_minus8[b_idx] == 2) {
-#if G_BYTE_ORDER == G_LITTLE_ENDIAN
-              g_value_set_string (&format, "P010_10LE");
-#else
-              g_value_set_string (&format, "P010_10BE");
-#endif
-            } else if (bitdepth_minus8[b_idx] == 4) {
-#if G_BYTE_ORDER == G_LITTLE_ENDIAN
-              g_value_set_string (&format, "P016_LE");
-#else
-              g_value_set_string (&format, "P016_BE");
-#endif
-            } else {
-              GST_WARNING ("unhandled bitdepth %d", bitdepth_minus8[b_idx] + 8);
-              break;
-            }
-            num_support++;
-            gst_value_list_append_value (&format_list, &format);
-            break;
-          case cudaVideoChromaFormat_444:
-            if (cudaVideoCodec_JPEG == codec_type) {
-              /* NVDEC jpeg decoder can decode 4:4:4 format
-               * but it produces 4:2:0 frame */
-              break;
-            }
-
-            if (bitdepth_minus8[b_idx] == 0) {
-              g_value_set_string (&format, "Y444");
-            } else if (bitdepth_minus8[b_idx] == 2 ||
-                bitdepth_minus8[b_idx] == 4) {
-#if G_BYTE_ORDER == G_LITTLE_ENDIAN
-              g_value_set_string (&format, "Y444_16LE");
-#else
-              g_value_set_string (&format, "Y444_16BE");
-#endif
-            } else {
-              GST_WARNING ("unhandled bitdepth %d", bitdepth_minus8[b_idx] + 8);
-              break;
-            }
-            num_support++;
-            gst_value_list_append_value (&format_list, &format);
-            break;
-          default:
-            break;
-        }
-      }
-    }
-
-    if (num_support == 0) {
-      GST_INFO ("device can not support %s", codec);
-      goto done;
-    }
-
-    src_templ = gst_caps_new_simple ("video/x-raw",
-        "width", GST_TYPE_INT_RANGE, min_width, max_width,
-        "height", GST_TYPE_INT_RANGE, min_height, max_height,
-        "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
-
-    gst_caps_set_value (src_templ, "format", &format_list);
-
-    /* OpenGL specific */
-#if HAVE_NVCODEC_GST_GL
-    {
-      GstCaps *gl_caps = gst_caps_copy (src_templ);
-      gst_caps_set_features_simple (gl_caps,
-          gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY));
-      gst_caps_append (src_templ, gl_caps);
-    }
-#endif
-
-    sink_templ = gst_caps_from_string (sink_caps_string);
-    gst_caps_set_simple (sink_templ,
-        "width", GST_TYPE_INT_RANGE, min_width, max_width,
-        "height", GST_TYPE_INT_RANGE, min_height, max_height, NULL);
-
-    if (gst_nvdec_get_supported_codec_profiles (&profile_list, codec_type,
-            format_flags)) {
-      gst_caps_set_value (sink_templ, "profile", &profile_list);
-    }
-
-    GST_DEBUG ("sink template caps %" GST_PTR_FORMAT, sink_templ);
-    GST_DEBUG ("src template caps %" GST_PTR_FORMAT, src_templ);
-
-    CuCtxPopCurrent (NULL);
-
-  done:
-    g_value_unset (&format_list);
-    g_value_unset (&format);
-    g_value_unset (&profile_list);
-
-    if (sink_templ && src_templ) {
-      gst_nvdec_subclass_register (plugin, type, codec_type, codec, device_idx,
-          rank, sink_templ, src_templ);
-    }
-
-    gst_clear_caps (&sink_templ);
-    gst_clear_caps (&src_templ);
-  }
-}
-
-typedef struct
-{
-  cudaVideoCodec codec;
-  const gchar *codec_name;
-  const gchar *sink_caps_string;
-} GstNvCodecMap;
-
-const GstNvCodecMap codec_map[] = {
-  {cudaVideoCodec_MPEG1, "mpegvideo",
-      "video/mpeg, mpegversion = (int) 1, systemstream = (boolean) false"},
-  {cudaVideoCodec_MPEG2, "mpeg2video",
-      "video/mpeg, mpegversion = (int) 2, systemstream = (boolean) false"},
-  {cudaVideoCodec_MPEG4, "mpeg4video",
-      "video/mpeg, mpegversion = (int) 4, systemstream = (boolean) false"},
-#if 0
-  /* FIXME: need verification */
-  {cudaVideoCodec_VC1, "vc1"},
-#endif
-  /* NOTE: common supported h264 profiles for all GPU architecture
-   * 4:2:0, baseline, main, and high profiles
-   */
-  {cudaVideoCodec_H264, "h264",
-      "video/x-h264, stream-format = (string) byte-stream"
-        ", alignment = (string) au"
-        ", profile = (string) { constrained-baseline, baseline, main, high }"},
-  {cudaVideoCodec_JPEG, "jpeg", "image/jpeg"},
-#if 0
-  /* FIXME: need verification */
-  {cudaVideoCodec_H264_SVC, "h264svc"},
-  {cudaVideoCodec_H264_MVC, "h264mvc"},
-#endif
-  {cudaVideoCodec_HEVC, "h265",
-      "video/x-h265, stream-format = (string) byte-stream"
-        ", alignment = (string) au, profile = (string) { main }"},
-  {cudaVideoCodec_VP8, "vp8", "video/x-vp8"},
-  {cudaVideoCodec_VP9, "vp9", "video/x-vp9"}
-};
-
 void
 gst_nvdec_plugin_init (GstPlugin * plugin, guint device_index,
-    CUcontext cuda_ctx)
+    cudaVideoCodec codec, const gchar * codec_name, GstCaps * sink_template,
+    GstCaps * src_template)
 {
-  gint i;
-
-  if (!gst_cuvid_can_get_decoder_caps ()) {
-    GstCaps *src_templ;
-
-    GST_INFO ("Too old nvidia driver to query decoder capability");
-
-    src_templ = gst_caps_from_string (GST_VIDEO_CAPS_MAKE ("NV12"));
-
-#if HAVE_NVCODEC_GST_GL
-    {
-      GstCaps *gl_caps = gst_caps_copy (src_templ);
-      gst_caps_set_features_simple (gl_caps,
-          gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY));
-      gst_caps_append (src_templ, gl_caps);
-    }
-#endif
-
-    for (i = 0; i < G_N_ELEMENTS (codec_map); i++) {
-      GstCaps *sink_templ;
-
-      sink_templ = gst_caps_from_string (codec_map[i].sink_caps_string);
-
-      gst_nvdec_subclass_register (plugin, GST_TYPE_NVDEC, codec_map[i].codec,
-          codec_map[i].codec_name, device_index, GST_RANK_PRIMARY,
-          sink_templ, src_templ);
-
-      gst_clear_caps (&sink_templ);
-    }
-
-    gst_clear_caps (&src_templ);
-
-    return;
-  }
-
-  for (i = 0; i < G_N_ELEMENTS (codec_map); i++) {
-    gst_nvdec_register (plugin, GST_TYPE_NVDEC, codec_map[i].codec,
-        codec_map[i].codec_name, codec_map[i].sink_caps_string,
-        GST_RANK_PRIMARY, device_index, cuda_ctx);
-  }
+  gst_nvdec_subclass_register (plugin, GST_TYPE_NVDEC, codec,
+      codec_name, device_index, GST_RANK_PRIMARY, sink_template, src_template);
 }
index bdace4cd124f9b9d9ba7ade20d765ee2b8869fd7..1b4d0836a67264b9f4973b3fbb24e5406a0e6b55 100644 (file)
@@ -104,7 +104,10 @@ GType gst_nvdec_get_type (void);
 
 void gst_nvdec_plugin_init (GstPlugin * plugin,
                             guint device_index,
-                            CUcontext cuda_ctx);
+                            cudaVideoCodec codec,
+                            const gchar * codec_name,
+                            GstCaps *sink_template,
+                            GstCaps *src_template);
 
 G_END_DECLS
 
diff --git a/sys/nvcodec/gstnvdecoder.c b/sys/nvcodec/gstnvdecoder.c
new file mode 100644 (file)
index 0000000..b7efcc0
--- /dev/null
@@ -0,0 +1,1102 @@
+/* GStreamer
+ * Copyright (C) 2017 Ericsson AB. All rights reserved.
+ * Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
+ *
+ * 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.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_NVCODEC_GST_GL
+#include <gst/gl/gl.h>
+#include <gst/gl/gstglfuncs.h>
+#endif
+
+#include "gstnvdecoder.h"
+#include <string.h>
+
+
+GST_DEBUG_CATEGORY_EXTERN (gst_nv_decoder_debug);
+#define GST_CAT_DEFAULT gst_nv_decoder_debug
+
+typedef struct _GstNvDecoderFrameInfo
+{
+  gboolean available;
+} GstNvDecoderFrameInfo;
+
+struct _GstNvDecoder
+{
+  GstObject parent;
+  GstCudaContext *context;
+  CUvideodecoder decoder_handle;
+
+  GstNvDecoderFrameInfo *frame_pool;
+  guint pool_size;
+
+  GstVideoInfo info;
+};
+
+static void gst_nv_decoder_dispose (GObject * object);
+static void gst_nv_decoder_finalize (GObject * object);
+
+#define parent_class gst_nv_decoder_parent_class
+G_DEFINE_TYPE (GstNvDecoder, gst_nv_decoder, GST_TYPE_OBJECT);
+
+static void
+gst_nv_decoder_class_init (GstNvDecoderClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->dispose = gst_nv_decoder_dispose;
+  gobject_class->finalize = gst_nv_decoder_finalize;
+}
+
+static void
+gst_nv_decoder_init (GstNvDecoder * self)
+{
+}
+
+static void
+gst_nv_decoder_dispose (GObject * object)
+{
+  GstNvDecoder *self = GST_NV_DECODER (object);
+
+  gst_clear_object (&self->context);
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_nv_decoder_finalize (GObject * object)
+{
+  GstNvDecoder *self = GST_NV_DECODER (object);
+
+  g_free (self->frame_pool);
+  if (self->decoder_handle)
+    gst_cuda_result (CuvidDestroyDecoder (self->decoder_handle));
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static cudaVideoChromaFormat
+chroma_format_from_video_format (GstVideoFormat format)
+{
+  switch (format) {
+    case GST_VIDEO_FORMAT_NV12:
+    case GST_VIDEO_FORMAT_P010_10LE:
+    case GST_VIDEO_FORMAT_P010_10BE:
+    case GST_VIDEO_FORMAT_P016_LE:
+    case GST_VIDEO_FORMAT_P016_BE:
+      return cudaVideoChromaFormat_420;
+    case GST_VIDEO_FORMAT_Y444:
+    case GST_VIDEO_FORMAT_Y444_16LE:
+    case GST_VIDEO_FORMAT_Y444_16BE:
+      return cudaVideoChromaFormat_444;
+    default:
+      g_assert_not_reached ();
+      break;
+  }
+
+  return cudaVideoChromaFormat_420;
+}
+
+static guint
+bitdepth_minus8_from_video_format (GstVideoFormat format)
+{
+  switch (format) {
+    case GST_VIDEO_FORMAT_NV12:
+    case GST_VIDEO_FORMAT_Y444:
+      return 0;
+    case GST_VIDEO_FORMAT_P010_10LE:
+    case GST_VIDEO_FORMAT_P010_10BE:
+      return 2;
+    case GST_VIDEO_FORMAT_P016_LE:
+    case GST_VIDEO_FORMAT_P016_BE:
+    case GST_VIDEO_FORMAT_Y444_16LE:
+    case GST_VIDEO_FORMAT_Y444_16BE:
+      return 8;
+    default:
+      g_assert_not_reached ();
+      break;
+  }
+
+  return 0;
+}
+
+static cudaVideoSurfaceFormat
+output_format_from_video_format (GstVideoFormat format)
+{
+  switch (format) {
+    case GST_VIDEO_FORMAT_NV12:
+      return cudaVideoSurfaceFormat_NV12;
+    case GST_VIDEO_FORMAT_P010_10LE:
+    case GST_VIDEO_FORMAT_P010_10BE:
+    case GST_VIDEO_FORMAT_P016_LE:
+    case GST_VIDEO_FORMAT_P016_BE:
+      return cudaVideoSurfaceFormat_P016;
+    case GST_VIDEO_FORMAT_Y444:
+      return cudaVideoSurfaceFormat_YUV444;
+    case GST_VIDEO_FORMAT_Y444_16LE:
+    case GST_VIDEO_FORMAT_Y444_16BE:
+      return cudaVideoSurfaceFormat_YUV444_16Bit;
+    default:
+      g_assert_not_reached ();
+      break;
+  }
+
+  return cudaVideoSurfaceFormat_NV12;
+}
+
+static gboolean
+gst_nv_decoder_prepare_frame_pool (GstNvDecoder * self, guint pool_size)
+{
+  gint i;
+
+  self->frame_pool = g_new (GstNvDecoderFrameInfo, pool_size);
+
+  for (i = 0; i < pool_size; i++)
+    self->frame_pool[i].available = TRUE;
+
+  self->pool_size = pool_size;
+
+  return TRUE;
+}
+
+GstNvDecoder *
+gst_nv_decoder_new (GstCudaContext * context, cudaVideoCodec codec,
+    GstVideoInfo * info, guint pool_size)
+{
+  GstNvDecoder *decoder;
+  CUVIDDECODECREATEINFO create_info = { 0, };
+  GstVideoFormat format;
+
+  g_return_val_if_fail (GST_IS_CUDA_CONTEXT (context), NULL);
+  g_return_val_if_fail (codec < cudaVideoCodec_NumCodecs, NULL);
+  g_return_val_if_fail (info != NULL, NULL);
+  g_return_val_if_fail (pool_size > 0, NULL);
+
+  decoder = g_object_new (GST_TYPE_NV_DECODER, NULL);
+  decoder->context = gst_object_ref (context);
+  gst_object_ref_sink (decoder);
+
+  format = GST_VIDEO_INFO_FORMAT (info);
+
+  /* FIXME: check aligned resolution or actaul coded resolution */
+  create_info.ulWidth = GST_VIDEO_INFO_WIDTH (info);;
+  create_info.ulHeight = GST_VIDEO_INFO_HEIGHT (info);;
+  create_info.ulNumDecodeSurfaces = pool_size;
+  create_info.CodecType = codec;
+  create_info.ChromaFormat = chroma_format_from_video_format (format);
+  create_info.ulCreationFlags = cudaVideoCreate_Default;
+  create_info.bitDepthMinus8 = bitdepth_minus8_from_video_format (format);
+  create_info.ulIntraDecodeOnly = 0;
+
+  create_info.display_area.left = 0;
+  create_info.display_area.top = 0;
+  create_info.display_area.right = GST_VIDEO_INFO_WIDTH (info);
+  create_info.display_area.bottom = GST_VIDEO_INFO_HEIGHT (info);
+  create_info.OutputFormat = output_format_from_video_format (format);
+  create_info.DeinterlaceMode = cudaVideoDeinterlaceMode_Weave;
+
+  create_info.ulTargetWidth = GST_VIDEO_INFO_WIDTH (info);;
+  create_info.ulTargetHeight = GST_VIDEO_INFO_HEIGHT (info);
+  /* we always copy decoded picture to output buffer */
+  create_info.ulNumOutputSurfaces = 1;
+
+  create_info.target_rect.left = 0;
+  create_info.target_rect.top = 0;
+  create_info.target_rect.right = GST_VIDEO_INFO_WIDTH (info);
+  create_info.target_rect.bottom = GST_VIDEO_INFO_HEIGHT (info);
+
+  if (!gst_cuda_context_push (context)) {
+    GST_ERROR_OBJECT (decoder, "Failed to lock CUDA context");
+    goto error;
+  }
+
+  if (!gst_cuda_result (CuvidCreateDecoder (&decoder->decoder_handle,
+              &create_info))) {
+    GST_ERROR_OBJECT (decoder, "Cannot create decoder instance");
+    goto error;
+  }
+
+  if (!gst_cuda_context_pop (NULL)) {
+    GST_ERROR_OBJECT (decoder, "Failed to unlock CUDA context");
+    goto error;
+  }
+
+  if (!gst_nv_decoder_prepare_frame_pool (decoder, pool_size)) {
+    GST_ERROR_OBJECT (decoder, "Cannot prepare internal surface buffer pool");
+    goto error;
+  }
+
+  decoder->info = *info;
+
+  return decoder;
+
+error:
+  gst_clear_object (&decoder);
+  return NULL;
+}
+
+GstNvDecoderFrame *
+gst_nv_decoder_new_frame (GstNvDecoder * decoder)
+{
+  GstNvDecoderFrame *frame;
+  gint i;
+  gint index_to_use = -1;
+
+  g_return_val_if_fail (GST_IS_NV_DECODER (decoder), NULL);
+
+  for (i = 0; i < decoder->pool_size; i++) {
+    if (decoder->frame_pool[i].available) {
+      decoder->frame_pool[i].available = FALSE;
+      index_to_use = i;
+      break;
+    }
+  }
+
+  if (index_to_use < 0) {
+    GST_ERROR_OBJECT (decoder, "No available frame");
+    return NULL;
+  }
+
+  frame = g_new0 (GstNvDecoderFrame, 1);
+  frame->index = index_to_use;
+  frame->decoder = gst_object_ref (decoder);
+
+  GST_LOG_OBJECT (decoder, "New frame %p (index %d)", frame, frame->index);
+
+  return frame;
+}
+
+/* must be called with gst_cuda_context_push */
+static gboolean
+gst_nv_decoder_frame_map (GstNvDecoderFrame * frame)
+{
+  GstNvDecoder *self;
+  CUVIDPROCPARAMS params = { 0 };
+
+  g_return_val_if_fail (frame != NULL, FALSE);
+  g_return_val_if_fail (frame->index >= 0, FALSE);
+  g_return_val_if_fail (GST_IS_NV_DECODER (frame->decoder), FALSE);
+
+  self = frame->decoder;
+
+  /* TODO: check interlaced */
+  params.progressive_frame = 1;
+
+  if (frame->mapped) {
+    GST_WARNING_OBJECT (self, "Frame %p is mapped already", frame);
+    return TRUE;
+  }
+
+  if (!gst_cuda_result (CuvidMapVideoFrame (self->decoder_handle,
+              frame->index, &frame->devptr, &frame->pitch, &params))) {
+    GST_ERROR_OBJECT (self, "Cannot map picture");
+    return FALSE;
+  }
+
+  frame->mapped = TRUE;
+
+  return TRUE;
+}
+
+/* must be called with gst_cuda_context_push */
+static void
+gst_nv_decoder_frame_unmap (GstNvDecoderFrame * frame)
+{
+  GstNvDecoder *self;
+
+  g_return_if_fail (frame != NULL);
+  g_return_if_fail (frame->index >= 0);
+  g_return_if_fail (GST_IS_NV_DECODER (frame->decoder));
+
+  self = frame->decoder;
+
+  if (!frame->mapped) {
+    GST_WARNING_OBJECT (self, "Frame %p is not mapped", frame);
+    return;
+  }
+
+  if (!gst_cuda_result (CuvidUnmapVideoFrame (self->decoder_handle,
+              frame->devptr))) {
+    GST_ERROR_OBJECT (self, "Cannot unmap picture");
+  }
+
+  frame->mapped = FALSE;
+}
+
+void
+gst_nv_decoder_frame_free (GstNvDecoderFrame * frame)
+{
+  GstNvDecoder *self;
+
+  g_assert (frame != NULL);
+
+  GST_LOG ("Free frame %p (index %d)", frame, frame->index);
+
+  if (frame->decoder) {
+    self = frame->decoder;
+    if (frame->mapped && gst_cuda_context_push (self->context)) {
+      gst_nv_decoder_frame_unmap (frame);
+      gst_cuda_context_pop (NULL);
+    }
+
+    if (frame->index < self->pool_size) {
+      self->frame_pool[frame->index].available = TRUE;
+    } else {
+      GST_WARNING_OBJECT (self,
+          "Frame %p has invalid index %d", frame, frame->index);
+    }
+
+    gst_object_unref (self);
+  }
+
+  g_free (frame);
+}
+
+gboolean
+gst_nv_decoder_decode_picture (GstNvDecoder * decoder, CUVIDPICPARAMS * params)
+{
+  GstCudaContext *ctx = decoder->context;
+  gboolean ret = TRUE;
+
+  GST_LOG_OBJECT (decoder, "picture index: %u", params->CurrPicIdx);
+
+  if (!gst_cuda_context_push (ctx)) {
+    GST_ERROR_OBJECT (decoder, "Failed to push CUDA context");
+    return FALSE;
+  }
+
+  if (!gst_cuda_result (CuvidDecodePicture (decoder->decoder_handle, params))) {
+    GST_ERROR_OBJECT (decoder, "Failed to decode picture");
+    ret = FALSE;
+  }
+
+  if (!gst_cuda_context_pop (NULL)) {
+    GST_WARNING_OBJECT (decoder, "Failed to pop CUDA context");
+  }
+
+  return ret;
+}
+
+#ifdef HAVE_NVCODEC_GST_GL
+static gboolean
+gst_nv_decoder_register_cuda_resource (GstNvDecoder * self, GstMemory * mem,
+    GstCudaGraphicsResource * resource)
+{
+  GstMapInfo info;
+  gboolean ret = FALSE;
+
+  if (!gst_cuda_context_push (self->context)) {
+    GST_ERROR_OBJECT (self, "Failed to push CUDA context");
+    return FALSE;
+  }
+
+  if (gst_memory_map (mem, &info, GST_MAP_READ | GST_MAP_GL)) {
+    GstGLMemoryPBO *gl_mem = (GstGLMemoryPBO *) mem;
+    GstGLBuffer *gl_buffer = gl_mem->pbo;
+
+    GST_LOG_OBJECT (self,
+        "Register glbuffer %d to CUDA resource", gl_buffer->id);
+
+    /* register resource without read/write only flags, since
+     * downstream CUDA elements (e.g., nvenc) might want to access
+     * this resource later. Instead, use map flags during map/unmap */
+    if (gst_cuda_graphics_resource_register_gl_buffer (resource,
+            gl_buffer->id, CU_GRAPHICS_REGISTER_FLAGS_NONE)) {
+      ret = TRUE;
+    } else {
+      GST_WARNING_OBJECT (self, "Failed to register memory");
+    }
+
+    gst_memory_unmap (mem, &info);
+  } else {
+    GST_WARNING_OBJECT (self, "Failed to map memory");
+  }
+
+  if (!gst_cuda_context_pop (NULL))
+    GST_WARNING_OBJECT (self, "Failed to pop CUDA context");
+
+  return ret;
+}
+
+static GstCudaGraphicsResource *
+gst_nv_decoder_ensure_cuda_graphics_resource (GstNvDecoder * self,
+    GstMemory * mem)
+{
+  GQuark quark;
+  GstCudaGraphicsResource *resource;
+
+  if (!gst_is_gl_memory_pbo (mem)) {
+    GST_WARNING_OBJECT (self, "memory is not GL PBO memory, %s",
+        mem->allocator->mem_type);
+    return NULL;
+  }
+
+  quark = gst_cuda_quark_from_id (GST_CUDA_QUARK_GRAPHICS_RESOURCE);
+
+  resource = (GstCudaGraphicsResource *)
+      gst_mini_object_get_qdata (GST_MINI_OBJECT (mem), quark);
+
+  if (!resource) {
+    gboolean ret;
+
+    resource = gst_cuda_graphics_resource_new (self->context,
+        GST_OBJECT (GST_GL_BASE_MEMORY_CAST (mem)->context),
+        GST_CUDA_GRAPHICS_RESOURCE_GL_BUFFER);
+
+    ret = gst_nv_decoder_register_cuda_resource (self, mem, resource);
+    if (!ret) {
+      GST_WARNING_OBJECT (self, "Couldn't register resource");
+      gst_cuda_graphics_resource_free (resource);
+
+      return NULL;
+    }
+
+    gst_mini_object_set_qdata (GST_MINI_OBJECT (mem), quark, resource,
+        (GDestroyNotify) gst_cuda_graphics_resource_free);
+  }
+
+  return resource;
+}
+
+typedef struct
+{
+  GstNvDecoder *self;
+  gboolean ret;
+  GstNvDecoderFrame *frame;
+  GstBuffer *output_buffer;
+} GstNvDecoderCopyToGLData;
+
+static void
+gst_nv_decoder_copy_frame_to_gl_internal (GstGLContext * context,
+    GstNvDecoderCopyToGLData * data)
+{
+  GstNvDecoder *self = data->self;
+  GstNvDecoderFrame *frame = data->frame;
+  GstCudaGraphicsResource **resources;
+  guint num_resources;
+  guint i;
+  CUDA_MEMCPY2D copy_params = { 0, };
+  GstVideoInfo *info = &self->info;
+
+  data->ret = TRUE;
+
+  num_resources = gst_buffer_n_memory (data->output_buffer);
+  resources = g_newa (GstCudaGraphicsResource *, num_resources);
+
+  for (i = 0; i < num_resources; i++) {
+    GstMemory *mem;
+
+    mem = gst_buffer_peek_memory (data->output_buffer, i);
+    resources[i] = gst_nv_decoder_ensure_cuda_graphics_resource (self, mem);
+    if (!resources[i]) {
+      GST_WARNING_OBJECT (self, "could not register %dth memory", i);
+      data->ret = FALSE;
+
+      return;
+    }
+
+    /* Need PBO -> texture */
+    GST_MINI_OBJECT_FLAG_SET (mem, GST_GL_BASE_MEMORY_TRANSFER_NEED_UPLOAD);
+  }
+
+  if (!gst_cuda_context_push (self->context)) {
+    GST_WARNING_OBJECT (self, "Failed to push CUDA context");
+    data->ret = FALSE;
+    return;
+  }
+
+  copy_params.srcMemoryType = CU_MEMORYTYPE_DEVICE;
+  copy_params.srcPitch = frame->pitch;
+  copy_params.dstMemoryType = CU_MEMORYTYPE_DEVICE;
+
+  for (i = 0; i < num_resources; i++) {
+    CUdeviceptr dst_ptr;
+    gsize size;
+    CUgraphicsResource cuda_resource =
+        gst_cuda_graphics_resource_map (resources[i], NULL,
+        CU_GRAPHICS_MAP_RESOURCE_FLAGS_WRITE_DISCARD);
+
+    if (!cuda_resource) {
+      GST_WARNING_OBJECT (self, "failed to map CUDA resources");
+      data->ret = FALSE;
+      goto unmap_video_frame;
+    }
+
+    if (!gst_cuda_result (CuGraphicsResourceGetMappedPointer (&dst_ptr, &size,
+                cuda_resource))) {
+      GST_WARNING_OBJECT (self, "failed to map CUDA resource");
+      data->ret = FALSE;
+      break;
+    }
+
+    copy_params.dstPitch = GST_VIDEO_INFO_PLANE_STRIDE (info, i);
+    copy_params.WidthInBytes = GST_VIDEO_INFO_COMP_WIDTH (info, i)
+        * GST_VIDEO_INFO_COMP_PSTRIDE (info, i);
+
+    copy_params.srcDevice = frame->devptr +
+        (i * frame->pitch * GST_VIDEO_INFO_HEIGHT (info));
+    copy_params.dstDevice = dst_ptr;
+    copy_params.Height = GST_VIDEO_INFO_COMP_HEIGHT (info, i);
+
+    if (!gst_cuda_result (CuMemcpy2DAsync (&copy_params, NULL))) {
+      GST_WARNING_OBJECT (self, "memcpy to mapped array failed");
+      data->ret = FALSE;
+    }
+  }
+
+  gst_cuda_result (CuStreamSynchronize (NULL));
+
+unmap_video_frame:
+  for (i = 0; i < num_resources; i++) {
+    gst_cuda_graphics_resource_unmap (resources[i], NULL);
+  }
+
+  if (!gst_cuda_context_pop (NULL))
+    GST_WARNING_OBJECT (self, "Failed to pop CUDA context");
+}
+
+static gboolean
+gst_nv_decoder_copy_frame_to_gl (GstNvDecoder * decoder,
+    GstGLContext * context, GstNvDecoderFrame * frame, GstBuffer * buffer)
+{
+  GstNvDecoderCopyToGLData data;
+
+  data.self = decoder;
+  data.frame = frame;
+  data.output_buffer = buffer;
+
+  gst_gl_context_thread_add (context,
+      (GstGLContextThreadFunc) gst_nv_decoder_copy_frame_to_gl_internal, &data);
+
+  GST_LOG_OBJECT (decoder, "Copy frame to GL ret %d", data.ret);
+
+  return data.ret;
+}
+#endif
+
+static gboolean
+gst_nv_decoder_copy_frame_to_system (GstNvDecoder * decoder,
+    GstNvDecoderFrame * frame, GstBuffer * buffer)
+{
+  GstVideoFrame video_frame;
+  CUDA_MEMCPY2D copy_params = { 0, };
+  gint i;
+  gboolean ret = FALSE;
+
+  if (!gst_video_frame_map (&video_frame, &decoder->info, buffer,
+          GST_MAP_WRITE)) {
+    GST_ERROR_OBJECT (decoder, "Couldn't map video frame");
+    return FALSE;
+  }
+
+  if (!gst_cuda_context_push (decoder->context)) {
+    GST_ERROR_OBJECT (decoder, "Failed to pust CUDA context");
+    gst_video_frame_unmap (&video_frame);
+    return FALSE;
+  }
+
+  copy_params.srcMemoryType = CU_MEMORYTYPE_DEVICE;
+  copy_params.srcPitch = frame->pitch;
+  copy_params.dstMemoryType = CU_MEMORYTYPE_HOST;
+  copy_params.WidthInBytes = GST_VIDEO_INFO_COMP_WIDTH (&decoder->info, 0)
+      * GST_VIDEO_INFO_COMP_PSTRIDE (&decoder->info, 0);
+
+  for (i = 0; i < GST_VIDEO_FRAME_N_PLANES (&video_frame); i++) {
+    copy_params.srcDevice = frame->devptr +
+        (i * frame->pitch * GST_VIDEO_INFO_HEIGHT (&decoder->info));
+    copy_params.dstHost = GST_VIDEO_FRAME_PLANE_DATA (&video_frame, i);
+    copy_params.dstPitch = GST_VIDEO_FRAME_PLANE_STRIDE (&video_frame, i);
+    copy_params.Height = GST_VIDEO_FRAME_COMP_HEIGHT (&video_frame, i);
+
+    if (!gst_cuda_result (CuMemcpy2DAsync (&copy_params, NULL))) {
+      GST_ERROR_OBJECT (decoder, "failed to copy %dth plane", i);
+      goto done;
+    }
+  }
+
+  gst_cuda_result (CuStreamSynchronize (NULL));
+
+  ret = TRUE;
+
+done:
+  gst_cuda_context_pop (NULL);
+
+  gst_video_frame_unmap (&video_frame);
+
+  GST_LOG_OBJECT (decoder, "Copy frame to system ret %d", ret);
+
+  return ret;
+}
+
+gboolean
+gst_nv_decoder_finish_frame (GstNvDecoder * decoder,
+    GstNvDecoderOutputType output_type, GstObject * graphics_context,
+    GstNvDecoderFrame * frame, GstBuffer * buffer)
+{
+  gboolean ret;
+
+  g_return_val_if_fail (GST_IS_NV_DECODER (decoder), FALSE);
+  g_return_val_if_fail (frame != NULL, FALSE);
+  g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE);
+
+#ifdef HAVE_NVCODEC_GST_GL
+  if (output_type == GST_NV_DECOCER_OUTPUT_TYPE_GL && !graphics_context) {
+    if (!GST_IS_GL_CONTEXT (graphics_context)) {
+      GST_ERROR_OBJECT (decoder, "Invalid GL Context");
+      return FALSE;
+    }
+  }
+#endif
+
+  if (!gst_cuda_context_push (decoder->context)) {
+    GST_ERROR_OBJECT (decoder, "Failed to pust CUDA context");
+    return FALSE;
+  }
+
+  if (!gst_nv_decoder_frame_map (frame)) {
+    GST_ERROR_OBJECT (decoder, "Couldn't map frame");
+    gst_cuda_context_pop (NULL);
+    return FALSE;
+  }
+
+  gst_cuda_context_pop (NULL);
+
+#ifdef HAVE_NVCODEC_GST_GL
+  if (output_type == GST_NV_DECOCER_OUTPUT_TYPE_GL) {
+    ret = gst_nv_decoder_copy_frame_to_gl (decoder,
+        GST_GL_CONTEXT (graphics_context), frame, buffer);
+  } else
+#endif
+  {
+    ret = gst_nv_decoder_copy_frame_to_system (decoder, frame, buffer);
+  }
+
+  gst_cuda_context_push (decoder->context);
+  gst_nv_decoder_frame_unmap (frame);
+  gst_cuda_context_pop (NULL);
+
+  return ret;
+}
+
+typedef enum
+{
+  GST_NV_DECODER_FORMAT_FLAG_NONE = (1 << 0),
+  GST_NV_DECODER_FORMAT_FLAG_420_8BITS = (1 << 1),
+  GST_NV_DECODER_FORMAT_FLAG_420_10BITS = (1 << 2),
+  GST_NV_DECODER_FORMAT_FLAG_420_12BITS = (1 << 3),
+  GST_NV_DECODER_FORMAT_FLAG_444_8BITS = (1 << 4),
+  GST_NV_DECODER_FORMAT_FLAG_444_10BITS = (1 << 5),
+  GST_NV_DECODER_FORMAT_FLAG_444_12BITS = (1 << 6),
+} GstNvDecoderFormatFlags;
+
+static gboolean
+gst_nv_decocer_get_supported_codec_profiles (GValue * profiles,
+    cudaVideoCodec codec, GstNvDecoderFormatFlags flags)
+{
+  GValue val = G_VALUE_INIT;
+  gboolean ret = FALSE;
+
+  g_value_init (&val, G_TYPE_STRING);
+
+  switch (codec) {
+    case cudaVideoCodec_H264:
+      if ((flags & GST_NV_DECODER_FORMAT_FLAG_420_8BITS) ==
+          GST_NV_DECODER_FORMAT_FLAG_420_8BITS) {
+        g_value_set_static_string (&val, "constrained-baseline");
+        gst_value_list_append_value (profiles, &val);
+
+        g_value_set_static_string (&val, "baseline");
+        gst_value_list_append_value (profiles, &val);
+
+        g_value_set_static_string (&val, "main");
+        gst_value_list_append_value (profiles, &val);
+
+        g_value_set_static_string (&val, "high");
+        gst_value_list_append_value (profiles, &val);
+      }
+
+      /* NVDEC supports only 4:2:0 8bits h264 decoding.
+       * following conditions are for the future enhancement */
+      if ((flags & GST_NV_DECODER_FORMAT_FLAG_420_10BITS) ==
+          GST_NV_DECODER_FORMAT_FLAG_420_10BITS) {
+        g_value_set_static_string (&val, "high-10");
+        gst_value_list_append_value (profiles, &val);
+      }
+
+      if ((flags & GST_NV_DECODER_FORMAT_FLAG_420_12BITS) ==
+          GST_NV_DECODER_FORMAT_FLAG_420_12BITS ||
+          (flags & GST_NV_DECODER_FORMAT_FLAG_444_8BITS) ==
+          GST_NV_DECODER_FORMAT_FLAG_444_8BITS ||
+          (flags & GST_NV_DECODER_FORMAT_FLAG_444_10BITS) ==
+          GST_NV_DECODER_FORMAT_FLAG_444_10BITS ||
+          (flags & GST_NV_DECODER_FORMAT_FLAG_444_12BITS) ==
+          GST_NV_DECODER_FORMAT_FLAG_444_12BITS) {
+        g_value_set_static_string (&val, "high-4:4:4");
+        gst_value_list_append_value (profiles, &val);
+      }
+
+      ret = TRUE;
+      break;
+    case cudaVideoCodec_HEVC:
+      if ((flags & GST_NV_DECODER_FORMAT_FLAG_420_8BITS) ==
+          GST_NV_DECODER_FORMAT_FLAG_420_8BITS) {
+        g_value_set_static_string (&val, "main");
+        gst_value_list_append_value (profiles, &val);
+      }
+
+      if ((flags & GST_NV_DECODER_FORMAT_FLAG_420_10BITS) ==
+          GST_NV_DECODER_FORMAT_FLAG_420_10BITS) {
+        g_value_set_static_string (&val, "main-10");
+        gst_value_list_append_value (profiles, &val);
+      }
+
+      if ((flags & GST_NV_DECODER_FORMAT_FLAG_420_12BITS) ==
+          GST_NV_DECODER_FORMAT_FLAG_420_12BITS) {
+        g_value_set_static_string (&val, "main-12");
+        gst_value_list_append_value (profiles, &val);
+      }
+
+      if ((flags & GST_NV_DECODER_FORMAT_FLAG_444_8BITS) ==
+          GST_NV_DECODER_FORMAT_FLAG_444_8BITS) {
+        g_value_set_static_string (&val, "main-444");
+        gst_value_list_append_value (profiles, &val);
+      }
+
+      if ((flags & GST_NV_DECODER_FORMAT_FLAG_444_10BITS) ==
+          GST_NV_DECODER_FORMAT_FLAG_444_10BITS) {
+        g_value_set_static_string (&val, "main-444-10");
+        gst_value_list_append_value (profiles, &val);
+      }
+
+      if ((flags & GST_NV_DECODER_FORMAT_FLAG_444_12BITS) ==
+          GST_NV_DECODER_FORMAT_FLAG_444_12BITS) {
+        g_value_set_static_string (&val, "main-444-12");
+        gst_value_list_append_value (profiles, &val);
+      }
+
+      ret = TRUE;
+      break;
+    default:
+      break;
+  }
+
+  g_value_unset (&val);
+
+  return ret;
+}
+
+typedef struct
+{
+  cudaVideoCodec codec;
+  const gchar *codec_name;
+  const gchar *sink_caps_string;
+} GstNvdecoderCodecMap;
+
+const GstNvdecoderCodecMap codec_map_list[] = {
+  {cudaVideoCodec_MPEG1, "mpegvideo",
+      "video/mpeg, mpegversion = (int) 1, systemstream = (boolean) false"},
+  {cudaVideoCodec_MPEG2, "mpeg2video",
+      "video/mpeg, mpegversion = (int) 2, systemstream = (boolean) false"},
+  {cudaVideoCodec_MPEG4, "mpeg4video",
+      "video/mpeg, mpegversion = (int) 4, systemstream = (boolean) false"},
+#if 0
+  /* FIXME: need verification */
+  {cudaVideoCodec_VC1, "vc1"},
+#endif
+  /* NOTE: common supported h264 profiles for all GPU architecture
+   * 4:2:0, baseline, main, and high profiles
+   */
+  {cudaVideoCodec_H264, "h264",
+      "video/x-h264, stream-format = (string) byte-stream"
+        ", alignment = (string) au"
+        ", profile = (string) { constrained-baseline, baseline, main, high }"},
+  {cudaVideoCodec_JPEG, "jpeg", "image/jpeg"},
+#if 0
+  /* FIXME: need verification */
+  {cudaVideoCodec_H264_SVC, "h264svc"},
+  {cudaVideoCodec_H264_MVC, "h264mvc"},
+#endif
+  {cudaVideoCodec_HEVC, "h265",
+      "video/x-h265, stream-format = (string) byte-stream"
+        ", alignment = (string) au, profile = (string) { main }"},
+  {cudaVideoCodec_VP8, "vp8", "video/x-vp8"},
+  {cudaVideoCodec_VP9, "vp9", "video/x-vp9"}
+};
+
+gboolean
+gst_nv_decoder_check_device_caps (CUcontext cuda_ctx, cudaVideoCodec codec,
+    GstCaps ** sink_template, GstCaps ** src_template)
+{
+  CUresult cuda_ret;
+  gint max_width = 0, min_width = G_MAXINT;
+  gint max_height = 0, min_height = G_MAXINT;
+  GstCaps *sink_templ = NULL;
+  GstCaps *src_templ = NULL;
+  /* FIXME: support 12bits format */
+  guint bitdepth_minus8[3] = { 0, 2, 4 };
+  GstNvDecoderFormatFlags format_flags = 0;
+  gint c_idx, b_idx;
+  guint num_support = 0;
+  cudaVideoChromaFormat chroma_list[] = {
+#if 0
+    /* FIXME: support monochrome */
+    cudaVideoChromaFormat_Monochrome,
+    /* FIXME: Can our OpenGL support NV16 and its 10/12bits variant?? */
+    cudaVideoChromaFormat_422,
+#endif
+    cudaVideoChromaFormat_420,
+    cudaVideoChromaFormat_444,
+  };
+  GValue format_list = G_VALUE_INIT;
+  GValue format = G_VALUE_INIT;
+  GValue profile_list = G_VALUE_INIT;
+  const GstNvdecoderCodecMap *codec_map = NULL;
+  guint i;
+  gboolean ret = FALSE;
+
+  for (i = 0; i < G_N_ELEMENTS (codec_map_list); i++) {
+    if (codec_map_list[i].codec == codec) {
+      codec_map = &codec_map_list[i];
+      break;
+    }
+  }
+
+  if (!codec_map) {
+    GST_INFO ("No codec map corresponding to codec %d", codec);
+    return FALSE;
+  }
+
+  if (!gst_cuvid_can_get_decoder_caps ()) {
+    GST_INFO ("Too old nvidia driver to query decoder capability");
+
+    src_templ = gst_caps_from_string (GST_VIDEO_CAPS_MAKE ("NV12"));
+
+#if HAVE_NVCODEC_GST_GL
+    {
+      GstCaps *gl_caps = gst_caps_copy (src_templ);
+      gst_caps_set_features_simple (gl_caps,
+          gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY));
+      gst_caps_append (src_templ, gl_caps);
+    }
+#endif
+
+    sink_templ = gst_caps_from_string (codec_map->sink_caps_string);
+
+    *src_template = src_templ;
+    *sink_template = sink_templ;
+
+    return TRUE;
+  }
+
+  g_value_init (&format_list, GST_TYPE_LIST);
+  g_value_init (&format, G_TYPE_STRING);
+  g_value_init (&profile_list, GST_TYPE_LIST);
+
+  if (CuCtxPushCurrent (cuda_ctx) != CUDA_SUCCESS)
+    goto done;
+
+  for (c_idx = 0; c_idx < G_N_ELEMENTS (chroma_list); c_idx++) {
+    for (b_idx = 0; b_idx < G_N_ELEMENTS (bitdepth_minus8); b_idx++) {
+      CUVIDDECODECAPS decoder_caps = { 0, };
+      GstNvDecoderFormatFlags cur_flag = 0;
+
+      decoder_caps.eCodecType = codec;
+      decoder_caps.eChromaFormat = chroma_list[c_idx];
+      decoder_caps.nBitDepthMinus8 = bitdepth_minus8[b_idx];
+
+      cuda_ret = CuvidGetDecoderCaps (&decoder_caps);
+      if (cuda_ret != CUDA_SUCCESS) {
+        GST_INFO ("could not query %s decoder capability, ret %d",
+            codec_map->codec_name, cuda_ret);
+        continue;
+      } else if (!decoder_caps.bIsSupported) {
+        GST_LOG ("%s bit-depth %d with chroma format %d is not supported",
+            codec_map->codec_name, bitdepth_minus8[b_idx] + 8, c_idx);
+        continue;
+      }
+
+      if (min_width > decoder_caps.nMinWidth)
+        min_width = decoder_caps.nMinWidth;
+      if (min_height > decoder_caps.nMinHeight)
+        min_height = decoder_caps.nMinHeight;
+      if (max_width < decoder_caps.nMaxWidth)
+        max_width = decoder_caps.nMaxWidth;
+      if (max_height < decoder_caps.nMaxHeight)
+        max_height = decoder_caps.nMaxHeight;
+
+      if (chroma_list[c_idx] == cudaVideoChromaFormat_420)
+        cur_flag = GST_NV_DECODER_FORMAT_FLAG_420_8BITS;
+      else
+        cur_flag = GST_NV_DECODER_FORMAT_FLAG_444_8BITS;
+
+      format_flags |= (cur_flag << (bitdepth_minus8[b_idx] / 2));
+
+      GST_INFO ("%s bit-depth %d with chroma format %d [%d - %d] x [%d - %d]",
+          codec_map->codec_name, bitdepth_minus8[b_idx] + 8, c_idx, min_width,
+          max_width, min_height, max_height);
+
+      switch (chroma_list[c_idx]) {
+        case cudaVideoChromaFormat_420:
+          if (bitdepth_minus8[b_idx] == 0) {
+            g_value_set_string (&format, "NV12");
+          } else if (bitdepth_minus8[b_idx] == 2) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+            g_value_set_string (&format, "P010_10LE");
+#else
+            g_value_set_string (&format, "P010_10BE");
+#endif
+          } else if (bitdepth_minus8[b_idx] == 4) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+            g_value_set_string (&format, "P016_LE");
+#else
+            g_value_set_string (&format, "P016_BE");
+#endif
+          } else {
+            GST_WARNING ("unhandled bitdepth %d", bitdepth_minus8[b_idx] + 8);
+            break;
+          }
+          num_support++;
+          gst_value_list_append_value (&format_list, &format);
+          break;
+        case cudaVideoChromaFormat_444:
+          if (cudaVideoCodec_JPEG == codec) {
+            /* NVDEC jpeg decoder can decode 4:4:4 format
+             * but it produces 4:2:0 frame */
+            break;
+          }
+
+          if (bitdepth_minus8[b_idx] == 0) {
+            g_value_set_string (&format, "Y444");
+          } else if (bitdepth_minus8[b_idx] == 2 || bitdepth_minus8[b_idx] == 4) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+            g_value_set_string (&format, "Y444_16LE");
+#else
+            g_value_set_string (&format, "Y444_16BE");
+#endif
+          } else {
+            GST_WARNING ("unhandled bitdepth %d", bitdepth_minus8[b_idx] + 8);
+            break;
+          }
+          num_support++;
+          gst_value_list_append_value (&format_list, &format);
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  if (num_support == 0) {
+    GST_INFO ("device can not support %s", codec_map->codec_name);
+    goto done;
+  }
+
+  src_templ = gst_caps_new_simple ("video/x-raw",
+      "width", GST_TYPE_INT_RANGE, min_width, max_width,
+      "height", GST_TYPE_INT_RANGE, min_height, max_height,
+      "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
+
+  gst_caps_set_value (src_templ, "format", &format_list);
+
+  /* OpenGL specific */
+#if HAVE_NVCODEC_GST_GL
+  {
+    GstCaps *gl_caps = gst_caps_copy (src_templ);
+    gst_caps_set_features_simple (gl_caps,
+        gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_GL_MEMORY));
+    gst_caps_append (src_templ, gl_caps);
+  }
+#endif
+
+  sink_templ = gst_caps_from_string (codec_map->sink_caps_string);
+  gst_caps_set_simple (sink_templ,
+      "width", GST_TYPE_INT_RANGE, min_width, max_width,
+      "height", GST_TYPE_INT_RANGE, min_height, max_height, NULL);
+
+  if (gst_nv_decocer_get_supported_codec_profiles (&profile_list, codec,
+          format_flags)) {
+    gst_caps_set_value (sink_templ, "profile", &profile_list);
+  }
+
+  GST_DEBUG ("sink template caps %" GST_PTR_FORMAT, sink_templ);
+  GST_DEBUG ("src template caps %" GST_PTR_FORMAT, src_templ);
+
+  CuCtxPopCurrent (NULL);
+
+done:
+  g_value_unset (&format_list);
+  g_value_unset (&format);
+  g_value_unset (&profile_list);
+
+  if (!sink_templ || !src_templ) {
+    gst_clear_caps (&sink_templ);
+    gst_clear_caps (&src_templ);
+
+    ret = FALSE;
+  } else {
+    /* class data will be leaked if the element never gets instantiated */
+    GST_MINI_OBJECT_FLAG_SET (src_templ, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED);
+    GST_MINI_OBJECT_FLAG_SET (sink_templ, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED);
+
+    *src_template = src_templ;
+    *sink_template = sink_templ;
+
+    ret = TRUE;
+  }
+
+  return ret;
+}
+
+const gchar *
+gst_cuda_video_codec_to_string (cudaVideoCodec codec)
+{
+  gint i;
+
+  for (i = 0; i < G_N_ELEMENTS (codec_map_list); i++) {
+    if (codec_map_list[i].codec == codec)
+      return codec_map_list[i].codec_name;
+  }
+
+  return "unknown";
+}
diff --git a/sys/nvcodec/gstnvdecoder.h b/sys/nvcodec/gstnvdecoder.h
new file mode 100644 (file)
index 0000000..e0bf968
--- /dev/null
@@ -0,0 +1,89 @@
+/* GStreamer
+ * Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_NV_DECODER_H__
+#define __GST_NV_DECODER_H__
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include "gstcudautils.h"
+#include "gstcuvidloader.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_NV_DECODER (gst_nv_decoder_get_type())
+G_DECLARE_FINAL_TYPE (GstNvDecoder,
+    gst_nv_decoder, GST, NV_DECODER, GstObject);
+
+typedef struct _GstNvDecoderFrame
+{
+  /* CUVIDPICPARAMS::CurrPicIdx */
+  gint index;
+  guintptr devptr;
+  guint pitch;
+
+  gboolean mapped;
+
+  /*< private >*/
+  GstNvDecoder *decoder;
+} GstNvDecoderFrame;
+
+typedef enum
+{
+  GST_NV_DECOCER_OUTPUT_TYPE_SYSTEM = 0,
+  GST_NV_DECOCER_OUTPUT_TYPE_GL,
+  /* FIXME: add support CUDA, D3D11 memory */
+} GstNvDecoderOutputType;
+
+G_GNUC_INTERNAL
+GstNvDecoder * gst_nv_decoder_new (GstCudaContext * context,
+                                   cudaVideoCodec codec,
+                                   GstVideoInfo * info,
+                                   guint pool_size);
+
+G_GNUC_INTERNAL
+GstNvDecoderFrame * gst_nv_decoder_new_frame (GstNvDecoder * decoder);
+
+G_GNUC_INTERNAL
+void gst_nv_decoder_frame_free (GstNvDecoderFrame * frame);
+
+G_GNUC_INTERNAL
+gboolean gst_nv_decoder_decode_picture (GstNvDecoder * decoder,
+                                        CUVIDPICPARAMS * params);
+
+G_GNUC_INTERNAL
+gboolean gst_nv_decoder_finish_frame (GstNvDecoder * decoder,
+                                      GstNvDecoderOutputType output_type,
+                                      GstObject * graphics_context,
+                                      GstNvDecoderFrame *frame,
+                                      GstBuffer *buffer);
+
+/* utils for class registration */
+G_GNUC_INTERNAL
+gboolean gst_nv_decoder_check_device_caps (CUcontext cuda_ctx,
+                                           cudaVideoCodec codec,
+                                           GstCaps **sink_template,
+                                           GstCaps **src_template);
+
+G_GNUC_INTERNAL
+const gchar * gst_cuda_video_codec_to_string (cudaVideoCodec codec);
+
+G_END_DECLS
+
+#endif /* __GST_NV_DECODER_H__ */
diff --git a/sys/nvcodec/gstnvh264dec.c b/sys/nvcodec/gstnvh264dec.c
new file mode 100644 (file)
index 0000000..a4a4dc3
--- /dev/null
@@ -0,0 +1,1076 @@
+/* GStreamer
+ * Copyright (C) 2017 Ericsson AB. All rights reserved.
+ * Copyright (C) 2019 Seungha Yang <seungha.yang@navercorp.com>
+ * Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
+ *
+ * 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.
+ *
+  * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2015 The Chromium Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *    * 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.
+ *    * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * 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
+
+#ifdef HAVE_NVCODEC_GST_GL
+#include <gst/gl/gl.h>
+#include <gst/gl/gstglfuncs.h>
+#endif
+
+#include "gstnvh264dec.h"
+#include "gstcudautils.h"
+#include "gstnvdecoder.h"
+
+#include <string.h>
+
+GST_DEBUG_CATEGORY_STATIC (gst_nv_h264_dec_debug);
+#define GST_CAT_DEFAULT gst_nv_h264_dec_debug
+
+#ifdef HAVE_NVCODEC_GST_GL
+#define SUPPORTED_GL_APIS (GST_GL_API_OPENGL | GST_GL_API_OPENGL3 | GST_GL_API_GLES2)
+#endif
+
+struct _GstNvH264Dec
+{
+  GstH264Decoder parent;
+
+  GstVideoCodecState *output_state;
+
+  const GstH264SPS *last_sps;
+  const GstH264PPS *last_pps;
+
+  GstCudaContext *context;
+  CUstream cuda_stream;
+  GstNvDecoder *decoder;
+  CUVIDPICPARAMS params;
+
+  /* slice buffer which will be passed to CUVIDPICPARAMS::pBitstreamData */
+  guint8 *bitstream_buffer;
+  /* allocated memory size of bitstream_buffer */
+  gsize bitstream_buffer_alloc_size;
+  /* current offset of bitstream_buffer (per frame) */
+  gsize bitstream_buffer_offset;
+
+  guint *slice_offsets;
+  guint slice_offsets_alloc_len;
+  guint num_slices;
+
+  guint width, height;
+  guint coded_width, coded_height;
+  guint bitdepth;
+  guint chroma_format_idc;
+  GstVideoFormat out_format;
+
+#ifdef HAVE_NVCODEC_GST_GL
+  GstGLDisplay *gl_display;
+  GstGLContext *gl_context;
+  GstGLContext *other_gl_context;
+#endif
+
+  GstNvDecoderOutputType output_type;
+};
+
+struct _GstNvH264DecClass
+{
+  GstH264DecoderClass parent_class;
+  guint cuda_device_id;
+};
+
+#define gst_nv_h264_dec_parent_class parent_class
+G_DEFINE_TYPE (GstNvH264Dec, gst_nv_h264_dec, GST_TYPE_H264_DECODER);
+
+static void gst_nv_h264_decoder_finalize (GObject * object);
+static void gst_nv_h264_dec_set_context (GstElement * element,
+    GstContext * context);
+static gboolean gst_nv_h264_dec_open (GstVideoDecoder * decoder);
+static gboolean gst_nv_h264_dec_close (GstVideoDecoder * decoder);
+static gboolean gst_nv_h264_dec_negotiate (GstVideoDecoder * decoder);
+static gboolean gst_nv_h264_dec_decide_allocation (GstVideoDecoder *
+    decoder, GstQuery * query);
+static gboolean gst_nv_h264_dec_src_query (GstVideoDecoder * decoder,
+    GstQuery * query);
+
+/* GstH264Decoder */
+static gboolean gst_nv_h264_dec_new_sequence (GstH264Decoder * decoder,
+    const GstH264SPS * sps, gint max_dpb_size);
+static gboolean gst_nv_h264_dec_new_picture (GstH264Decoder * decoder,
+    GstH264Picture * picture);
+static GstFlowReturn gst_nv_h264_dec_output_picture (GstH264Decoder *
+    decoder, GstH264Picture * picture);
+static gboolean gst_nv_h264_dec_start_picture (GstH264Decoder * decoder,
+    GstH264Picture * picture, GstH264Slice * slice, GstH264Dpb * dpb);
+static gboolean gst_nv_h264_dec_decode_slice (GstH264Decoder * decoder,
+    GstH264Picture * picture, GstH264Slice * slice);
+static gboolean gst_nv_h264_dec_end_picture (GstH264Decoder * decoder,
+    GstH264Picture * picture);
+
+static void
+gst_nv_h264_dec_class_init (GstNvH264DecClass * klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+  GstVideoDecoderClass *decoder_class = GST_VIDEO_DECODER_CLASS (klass);
+  GstH264DecoderClass *h264decoder_class = GST_H264_DECODER_CLASS (klass);
+
+  object_class->finalize = gst_nv_h264_decoder_finalize;
+
+  element_class->set_context = GST_DEBUG_FUNCPTR (gst_nv_h264_dec_set_context);
+
+  decoder_class->open = GST_DEBUG_FUNCPTR (gst_nv_h264_dec_open);
+  decoder_class->close = GST_DEBUG_FUNCPTR (gst_nv_h264_dec_close);
+  decoder_class->negotiate = GST_DEBUG_FUNCPTR (gst_nv_h264_dec_negotiate);
+  decoder_class->decide_allocation =
+      GST_DEBUG_FUNCPTR (gst_nv_h264_dec_decide_allocation);
+  decoder_class->src_query = GST_DEBUG_FUNCPTR (gst_nv_h264_dec_src_query);
+
+  h264decoder_class->new_sequence =
+      GST_DEBUG_FUNCPTR (gst_nv_h264_dec_new_sequence);
+  h264decoder_class->new_picture =
+      GST_DEBUG_FUNCPTR (gst_nv_h264_dec_new_picture);
+  h264decoder_class->output_picture =
+      GST_DEBUG_FUNCPTR (gst_nv_h264_dec_output_picture);
+  h264decoder_class->start_picture =
+      GST_DEBUG_FUNCPTR (gst_nv_h264_dec_start_picture);
+  h264decoder_class->decode_slice =
+      GST_DEBUG_FUNCPTR (gst_nv_h264_dec_decode_slice);
+  h264decoder_class->end_picture =
+      GST_DEBUG_FUNCPTR (gst_nv_h264_dec_end_picture);
+
+  GST_DEBUG_CATEGORY_INIT (gst_nv_h264_dec_debug,
+      "nvh264dec", 0, "Nvidia H.264 Decoder");
+}
+
+static void
+gst_nv_h264_dec_init (GstNvH264Dec * self)
+{
+}
+
+static void
+gst_nv_h264_decoder_finalize (GObject * object)
+{
+  GstNvH264Dec *self = GST_NV_H264_DEC (object);
+
+  g_free (self->bitstream_buffer);
+  g_free (self->slice_offsets);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_nv_h264_dec_set_context (GstElement * element, GstContext * context)
+{
+  GstNvH264Dec *self = GST_NV_H264_DEC (element);
+  GstNvH264DecClass *klass = GST_NV_H264_DEC_GET_CLASS (self);
+
+  GST_DEBUG_OBJECT (self, "set context %s",
+      gst_context_get_context_type (context));
+
+  if (gst_cuda_handle_set_context (element, context, klass->cuda_device_id,
+          &self->context)) {
+    goto done;
+  }
+#ifdef HAVE_NVCODEC_GST_GL
+  gst_gl_handle_set_context (element, context, &self->gl_display,
+      &self->other_gl_context);
+#endif
+
+done:
+  GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
+}
+
+static gboolean
+gst_nv_h264_dec_open (GstVideoDecoder * decoder)
+{
+  GstNvH264Dec *self = GST_NV_H264_DEC (decoder);
+  CUresult cuda_ret;
+
+  if (!gst_cuda_ensure_element_context (GST_ELEMENT_CAST (decoder),
+          0, &self->context)) {
+    GST_ERROR_OBJECT (self, "failed to create CUDA context");
+    return FALSE;
+  }
+
+  if (gst_cuda_context_push (self->context)) {
+    cuda_ret = CuStreamCreate (&self->cuda_stream, CU_STREAM_DEFAULT);
+    if (!gst_cuda_result (cuda_ret)) {
+      GST_WARNING_OBJECT (self,
+          "Could not create CUDA stream, will use default stream");
+      self->cuda_stream = NULL;
+    }
+    gst_cuda_context_pop (NULL);
+  }
+#if HAVE_NVCODEC_GST_GL
+  gst_gl_ensure_element_data (GST_ELEMENT (self),
+      &self->gl_display, &self->other_gl_context);
+  if (self->gl_display)
+    gst_gl_display_filter_gl_api (GST_GL_DISPLAY (self->gl_display),
+        SUPPORTED_GL_APIS);
+#endif
+
+  return TRUE;
+}
+
+static gboolean
+gst_nv_h264_dec_close (GstVideoDecoder * decoder)
+{
+  GstNvH264Dec *self = GST_NV_H264_DEC (decoder);
+
+  g_clear_pointer (&self->output_state, gst_video_codec_state_unref);
+  gst_clear_object (&self->decoder);
+
+  if (self->context && self->cuda_stream) {
+    if (gst_cuda_context_push (self->context)) {
+      gst_cuda_result (CuStreamDestroy (self->cuda_stream));
+      gst_cuda_context_pop (NULL);
+    }
+  }
+#ifdef HAVE_NVCODEC_GST_GL
+  gst_clear_object (&self->gl_context);
+  gst_clear_object (&self->other_gl_context);
+  gst_clear_object (&self->gl_display);
+#endif
+
+  gst_clear_object (&self->context);
+  self->cuda_stream = NULL;
+
+  return TRUE;
+}
+
+#ifdef HAVE_NVCODEC_GST_GL
+static void
+gst_nv_h264_dec_check_cuda_device_from_context (GstGLContext * context,
+    gboolean * ret)
+{
+  guint device_count = 0;
+  CUdevice device_list[1] = { 0, };
+  CUresult cuda_ret;
+
+  *ret = FALSE;
+
+  cuda_ret = CuGLGetDevices (&device_count,
+      device_list, 1, CU_GL_DEVICE_LIST_ALL);
+
+  if (!gst_cuda_result (cuda_ret) || device_count == 0)
+    return;
+
+  *ret = TRUE;
+
+  return;
+}
+
+static gboolean
+gst_nv_h264_dec_ensure_gl_context (GstNvH264Dec * self)
+{
+  gboolean ret;
+
+  if (!self->gl_display) {
+    GST_DEBUG_OBJECT (self, "No available OpenGL display");
+    return FALSE;
+  }
+
+  if (!gst_gl_query_local_gl_context (GST_ELEMENT (self), GST_PAD_SRC,
+          &self->gl_context)) {
+    GST_INFO_OBJECT (self, "failed to query local OpenGL context");
+    if (self->gl_context)
+      gst_object_unref (self->gl_context);
+    self->gl_context =
+        gst_gl_display_get_gl_context_for_thread (self->gl_display, NULL);
+    if (!self->gl_context
+        || !gst_gl_display_add_context (self->gl_display, self->gl_context)) {
+      if (self->gl_context)
+        gst_object_unref (self->gl_context);
+      if (!gst_gl_display_create_context (self->gl_display,
+              self->other_gl_context, &self->gl_context, NULL)) {
+        GST_ERROR_OBJECT (self, "failed to create OpenGL context");
+        return FALSE;
+      }
+      if (!gst_gl_display_add_context (self->gl_display, self->gl_context)) {
+        GST_ERROR_OBJECT (self,
+            "failed to add the OpenGL context to the display");
+        return FALSE;
+      }
+    }
+  }
+
+  if (!gst_gl_context_check_gl_version (self->gl_context,
+          SUPPORTED_GL_APIS, 3, 0)) {
+    GST_WARNING_OBJECT (self, "OpenGL context could not support PBO download");
+    return FALSE;
+  }
+
+  gst_gl_context_thread_add (self->gl_context,
+      (GstGLContextThreadFunc) gst_nv_h264_dec_check_cuda_device_from_context,
+      &ret);
+
+  if (!ret) {
+    GST_WARNING_OBJECT (self, "Current OpenGL context is not CUDA-compatible");
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+#endif
+
+static gboolean
+gst_nv_h264_dec_negotiate (GstVideoDecoder * decoder)
+{
+  GstNvH264Dec *self = GST_NV_H264_DEC (decoder);
+  GstH264Decoder *h264dec = GST_H264_DECODER (decoder);
+  GstVideoCodecState *state;
+
+  GST_DEBUG_OBJECT (self, "negotiate");
+
+  state = gst_video_decoder_set_output_state (GST_VIDEO_DECODER (self),
+      self->out_format, self->width, self->height, h264dec->input_state);
+
+  state->caps = gst_video_info_to_caps (&state->info);
+
+  self->output_type = GST_NV_DECOCER_OUTPUT_TYPE_SYSTEM;
+
+#ifdef HAVE_NVCODEC_GST_GL
+  {
+    GstCaps *caps;
+    caps = gst_pad_get_allowed_caps (GST_VIDEO_DECODER_SRC_PAD (self));
+    GST_DEBUG_OBJECT (self, "Allowed caps %" GST_PTR_FORMAT, caps);
+
+    if (!caps || gst_caps_is_any (caps)) {
+      GST_DEBUG_OBJECT (self,
+          "cannot determine output format, use system memory");
+    } else if (self->gl_display) {
+      GstCapsFeatures *features;
+      guint size = gst_caps_get_size (caps);
+      guint i;
+
+      for (i = 0; i < size; i++) {
+        features = gst_caps_get_features (caps, i);
+        if (features && gst_caps_features_contains (features,
+                GST_CAPS_FEATURE_MEMORY_GL_MEMORY)) {
+          GST_DEBUG_OBJECT (self, "found GL memory feature, use gl");
+          self->output_type = GST_NV_DECOCER_OUTPUT_TYPE_GL;
+          break;
+        }
+      }
+    }
+    gst_clear_caps (&caps);
+  }
+
+  if (self->output_type == GST_NV_DECOCER_OUTPUT_TYPE_GL &&
+      !gst_nv_h264_dec_ensure_gl_context (self)) {
+    GST_WARNING_OBJECT (self,
+        "OpenGL context is not CUDA-compatible, fallback to system memory");
+    self->output_type = GST_NV_DECOCER_OUTPUT_TYPE_SYSTEM;
+  }
+
+  if (self->output_type == GST_NV_DECOCER_OUTPUT_TYPE_GL) {
+    gst_caps_set_features (state->caps, 0,
+        gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, NULL));
+    gst_caps_set_simple (state->caps, "texture-target", G_TYPE_STRING,
+        "2D", NULL);
+  } else {
+    GST_DEBUG_OBJECT (self, "use system memory");
+  }
+#endif
+
+  if (self->output_state)
+    gst_video_codec_state_unref (self->output_state);
+
+  self->output_state = state;
+
+  /* TODO: add support D3D11 memory */
+
+  return GST_VIDEO_DECODER_CLASS (parent_class)->negotiate (decoder);
+}
+
+static gboolean
+gst_nv_h264_dec_decide_allocation (GstVideoDecoder * decoder, GstQuery * query)
+{
+#ifdef HAVE_NVCODEC_GST_GL
+  GstNvH264Dec *self = GST_NV_H264_DEC (decoder);
+  GstCaps *outcaps;
+  GstBufferPool *pool = NULL;
+  guint n, size, min, max;
+  GstVideoInfo vinfo = { 0, };
+  GstStructure *config;
+
+  GST_DEBUG_OBJECT (self, "decide allocation");
+
+  if (self->output_type == GST_NV_DECOCER_OUTPUT_TYPE_SYSTEM)
+    return GST_VIDEO_DECODER_CLASS (parent_class)->decide_allocation
+        (decoder, query);
+
+  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 (pool && !GST_IS_GL_BUFFER_POOL (pool)) {
+    gst_object_unref (pool);
+    pool = NULL;
+  }
+
+  if (!pool) {
+    pool = gst_gl_buffer_pool_new (self->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);
+#endif
+
+  return GST_VIDEO_DECODER_CLASS (parent_class)->decide_allocation
+      (decoder, query);
+}
+
+static gboolean
+gst_nv_h264_dec_src_query (GstVideoDecoder * decoder, GstQuery * query)
+{
+  GstNvH264Dec *self = GST_NV_H264_DEC (decoder);
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_CONTEXT:
+      if (gst_cuda_handle_context_query (GST_ELEMENT (decoder),
+              query, self->context)) {
+        return TRUE;
+      }
+#ifdef HAVE_NVCODEC_GST_GL
+      if (gst_gl_handle_context_query (GST_ELEMENT (decoder), query,
+              self->gl_display, self->gl_context, self->other_gl_context)) {
+        if (self->gl_display)
+          gst_gl_display_filter_gl_api (GST_GL_DISPLAY (self->gl_display),
+              SUPPORTED_GL_APIS);
+        return TRUE;
+      }
+#endif
+      break;
+    default:
+      break;
+  }
+
+  return GST_VIDEO_DECODER_CLASS (parent_class)->src_query (decoder, query);
+}
+
+static gboolean
+gst_nv_h264_dec_new_sequence (GstH264Decoder * decoder, const GstH264SPS * sps,
+    gint max_dpb_size)
+{
+  GstNvH264Dec *self = GST_NV_H264_DEC (decoder);
+  gint crop_width, crop_height;
+  gboolean modified = FALSE;
+
+  GST_LOG_OBJECT (self, "new sequence");
+
+  if (sps->frame_cropping_flag) {
+    crop_width = sps->crop_rect_width;
+    crop_height = sps->crop_rect_height;
+  } else {
+    crop_width = sps->width;
+    crop_height = sps->height;
+  }
+
+  if (self->width != crop_width || self->height != crop_height ||
+      self->coded_width != sps->width || self->coded_height != sps->height) {
+    GST_INFO_OBJECT (self, "resolution changed %dx%d (%dx%d)",
+        crop_width, crop_height, sps->width, sps->height);
+    self->width = crop_width;
+    self->height = crop_height;
+    self->coded_width = sps->width;
+    self->coded_height = sps->height;
+    modified = TRUE;
+  }
+
+  if (self->bitdepth != sps->bit_depth_luma_minus8 + 8) {
+    GST_INFO_OBJECT (self, "bitdepth changed");
+    self->bitdepth = sps->bit_depth_luma_minus8 + 8;
+    modified = TRUE;
+  }
+
+  if (self->chroma_format_idc != sps->chroma_format_idc) {
+    GST_INFO_OBJECT (self, "chroma format changed");
+    self->chroma_format_idc = sps->chroma_format_idc;
+    modified = TRUE;
+  }
+
+  if (modified || !self->decoder) {
+    GstVideoInfo info;
+
+    self->out_format = GST_VIDEO_FORMAT_UNKNOWN;
+
+    if (self->bitdepth == 8) {
+      if (self->chroma_format_idc == 1)
+        self->out_format = GST_VIDEO_FORMAT_NV12;
+      else {
+        GST_FIXME_OBJECT (self, "Could not support 8bits non-4:2:0 format");
+      }
+    } else if (self->bitdepth == 10) {
+      if (self->chroma_format_idc == 1)
+        self->out_format = GST_VIDEO_FORMAT_P010_10LE;
+      else {
+        GST_FIXME_OBJECT (self, "Could not support 10bits non-4:2:0 format");
+      }
+    }
+
+    if (self->out_format == GST_VIDEO_FORMAT_UNKNOWN) {
+      GST_ERROR_OBJECT (self, "Could not support bitdepth/chroma format");
+      return FALSE;
+    }
+
+    gst_clear_object (&self->decoder);
+
+    gst_video_info_set_format (&info,
+        self->out_format, self->width, self->height);
+
+    /* FIXME: add support cudaVideoCodec_H264_SVC and cudaVideoCodec_H264_MVC */
+    self->decoder = gst_nv_decoder_new (self->context, cudaVideoCodec_H264,
+        &info,
+        /* Additional 2 buffers for margin */
+        max_dpb_size + 2);
+
+    if (!self->decoder) {
+      GST_ERROR_OBJECT (self, "Failed to create decoder");
+      return FALSE;
+    }
+
+    if (!gst_video_decoder_negotiate (GST_VIDEO_DECODER (self))) {
+      GST_ERROR_OBJECT (self, "Failed to negotiate with downstream");
+      return FALSE;
+    }
+
+    self->last_sps = NULL;
+    self->last_pps = NULL;
+    memset (&self->params, 0, sizeof (CUVIDPICPARAMS));
+  }
+
+  return TRUE;
+}
+
+static gboolean
+gst_nv_h264_dec_new_picture (GstH264Decoder * decoder, GstH264Picture * picture)
+{
+  GstNvH264Dec *self = GST_NV_H264_DEC (decoder);
+  GstNvDecoderFrame *frame;
+
+  frame = gst_nv_decoder_new_frame (self->decoder);
+  if (!frame) {
+    GST_ERROR_OBJECT (self, "No available decoder frame");
+    return FALSE;
+  }
+
+  GST_LOG_OBJECT (self, "New decoder frame %p (index %d)", frame, frame->index);
+
+  gst_h264_picture_set_user_data (picture,
+      frame, (GDestroyNotify) gst_nv_decoder_frame_free);
+
+  return TRUE;
+}
+
+static GstFlowReturn
+gst_nv_h264_dec_output_picture (GstH264Decoder * decoder,
+    GstH264Picture * picture)
+{
+  GstNvH264Dec *self = GST_NV_H264_DEC (decoder);
+  GstVideoCodecFrame *frame = NULL;
+  GstBuffer *output_buffer = NULL;
+  GstNvDecoderFrame *decoder_frame;
+  gboolean ret = FALSE;
+
+  GST_LOG_OBJECT (self,
+      "Outputting picture %p (poc %d)", picture, picture->pic_order_cnt);
+
+  decoder_frame =
+      (GstNvDecoderFrame *) gst_h264_picture_get_user_data (picture);
+  if (!decoder_frame) {
+    GST_ERROR_OBJECT (self, "No decoder frame in picture %p", picture);
+    return GST_FLOW_ERROR;
+  }
+
+  frame = gst_video_decoder_get_frame (GST_VIDEO_DECODER (self),
+      picture->system_frame_number);
+  if (!frame) {
+    GST_ERROR_OBJECT (self, "Failed to retrieve codec frame");
+    return GST_FLOW_ERROR;
+  }
+
+  output_buffer =
+      gst_video_decoder_allocate_output_buffer (GST_VIDEO_DECODER (self));
+  frame->output_buffer = output_buffer;
+
+#ifdef HAVE_NVCODEC_GST_GL
+  if (self->output_type == GST_NV_DECOCER_OUTPUT_TYPE_GL) {
+    ret = gst_nv_decoder_finish_frame (self->decoder,
+        GST_NV_DECOCER_OUTPUT_TYPE_GL, GST_OBJECT (self->gl_context),
+        decoder_frame, output_buffer);
+
+    /* FIXME: This is the case where OpenGL context of downstream glbufferpool
+     * belongs to non-nvidia (or different device).
+     * There should be enhancement to ensure nvdec has compatible OpenGL context
+     */
+    if (!ret) {
+      GST_WARNING_OBJECT (self,
+          "Couldn't copy frame to GL memory, fallback to system memory");
+      self->output_type = GST_NV_DECOCER_OUTPUT_TYPE_SYSTEM;
+    }
+  }
+
+  if (!ret)
+#endif
+  {
+    if (!gst_nv_decoder_finish_frame (self->decoder,
+            GST_NV_DECOCER_OUTPUT_TYPE_SYSTEM, NULL, decoder_frame,
+            output_buffer)) {
+      GST_ERROR_OBJECT (self, "Failed to finish frame");
+      gst_video_decoder_drop_frame (GST_VIDEO_DECODER (self), frame);
+      return GST_FLOW_ERROR;
+    }
+  }
+
+  return gst_video_decoder_finish_frame (GST_VIDEO_DECODER (self), frame);
+}
+
+static GstNvDecoderFrame *
+gst_nv_h264_dec_get_decoder_frame_from_picture (GstNvH264Dec * self,
+    GstH264Picture * picture)
+{
+  GstNvDecoderFrame *frame;
+
+  frame = (GstNvDecoderFrame *) gst_h264_picture_get_user_data (picture);
+
+  if (!frame)
+    GST_DEBUG_OBJECT (self, "current picture does not have decoder frame");
+
+  return frame;
+}
+
+static void
+gst_nv_h264_dec_picture_params_from_sps (GstNvH264Dec * self,
+    const GstH264SPS * sps, gboolean field_pic, CUVIDH264PICPARAMS * params)
+{
+  params->residual_colour_transform_flag = sps->separate_colour_plane_flag;
+  params->MbaffFrameFlag = sps->mb_adaptive_frame_field_flag && field_pic;
+
+#define COPY_FIELD(f) \
+  (params)->f = (sps)->f
+
+  COPY_FIELD (log2_max_frame_num_minus4);
+  COPY_FIELD (pic_order_cnt_type);
+  COPY_FIELD (log2_max_pic_order_cnt_lsb_minus4);
+  COPY_FIELD (delta_pic_order_always_zero_flag);
+  COPY_FIELD (frame_mbs_only_flag);
+  COPY_FIELD (direct_8x8_inference_flag);
+  COPY_FIELD (num_ref_frames);
+  COPY_FIELD (bit_depth_luma_minus8);
+  COPY_FIELD (bit_depth_chroma_minus8);
+  COPY_FIELD (qpprime_y_zero_transform_bypass_flag);
+
+#undef COPY_FIELD
+}
+
+static void
+gst_nv_h264_dec_picture_params_from_pps (GstNvH264Dec * self,
+    const GstH264PPS * pps, CUVIDH264PICPARAMS * params)
+{
+  params->second_chroma_qp_index_offset =
+      (gint8) pps->second_chroma_qp_index_offset;
+
+#define COPY_FIELD(f) \
+  (params)->f = (pps)->f
+
+  COPY_FIELD (entropy_coding_mode_flag);
+  COPY_FIELD (pic_order_present_flag);
+  COPY_FIELD (num_ref_idx_l0_active_minus1);
+  COPY_FIELD (num_ref_idx_l1_active_minus1);
+  COPY_FIELD (pic_init_qp_minus26);
+  COPY_FIELD (weighted_pred_flag);
+  COPY_FIELD (weighted_bipred_idc);
+  COPY_FIELD (pic_init_qp_minus26);
+  COPY_FIELD (deblocking_filter_control_present_flag);
+  COPY_FIELD (redundant_pic_cnt_present_flag);
+  COPY_FIELD (transform_8x8_mode_flag);
+  COPY_FIELD (constrained_intra_pred_flag);
+  COPY_FIELD (chroma_qp_index_offset);
+#undef COPY_FIELD
+
+  /* h264parser will copy scaling list from sps to pps regardless of
+   * pic_scaling_matrix_present_flag */
+  g_assert (sizeof (params->WeightScale4x4) == sizeof (pps->scaling_lists_4x4));
+  g_assert (sizeof (params->WeightScale8x8[0]) ==
+      sizeof (pps->scaling_lists_8x8[0]));
+  memcpy (params->WeightScale4x4, pps->scaling_lists_4x4,
+      sizeof (params->WeightScale4x4));
+  memcpy (params->WeightScale8x8[0], pps->scaling_lists_8x8[0],
+      sizeof (params->WeightScale8x8[0]));
+  memcpy (params->WeightScale8x8[1], pps->scaling_lists_8x8[3],
+      sizeof (params->WeightScale8x8[0]));
+}
+
+static void
+gst_nv_h264_dec_reset_bitstream_params (GstNvH264Dec * self)
+{
+  self->bitstream_buffer_offset = 0;
+  self->num_slices = 0;
+
+  self->params.nBitstreamDataLen = 0;
+  self->params.pBitstreamData = NULL;
+  self->params.nNumSlices = 0;
+  self->params.pSliceDataOffsets = NULL;
+}
+
+static gboolean
+gst_nv_h264_dec_start_picture (GstH264Decoder * decoder,
+    GstH264Picture * picture, GstH264Slice * slice, GstH264Dpb * dpb)
+{
+  GstNvH264Dec *self = GST_NV_H264_DEC (decoder);
+  CUVIDPICPARAMS *params = &self->params;
+  CUVIDH264PICPARAMS *h264_params = &params->CodecSpecific.h264;
+  const GstH264SliceHdr *slice_header = &slice->header;
+  const GstH264SPS *sps;
+  const GstH264PPS *pps;
+  GstNvDecoderFrame *frame;
+  GArray *dpb_array;
+  gint i;
+
+  g_return_val_if_fail (slice_header->pps != NULL, FALSE);
+  g_return_val_if_fail (slice_header->pps->sequence != NULL, FALSE);
+
+  frame = gst_nv_h264_dec_get_decoder_frame_from_picture (self, picture);
+
+  if (!frame) {
+    GST_ERROR_OBJECT (self,
+        "Couldn't get decoder frame frame picture %p", picture);
+    return FALSE;
+  }
+
+  gst_nv_h264_dec_reset_bitstream_params (self);
+
+  sps = slice_header->pps->sequence;
+  pps = slice_header->pps;
+
+  /* FIXME: update sps/pps related params only when it's required */
+  params->PicWidthInMbs = sps->pic_width_in_mbs_minus1 + 1;
+  params->FrameHeightInMbs = sps->pic_height_in_map_units_minus1 + 1;
+  params->CurrPicIdx = frame->index;
+  /* TODO: verifiy interlaced */
+  params->field_pic_flag = picture->field != GST_H264_PICTURE_FIELD_FRAME;
+  params->bottom_field_flag =
+      picture->field == GST_H264_PICTURE_FIELD_BOTTOM_FIELD;
+  /* TODO: set second_field here */
+  params->second_field = 0;
+
+  /* nBitstreamDataLen, pBitstreamData, nNumSlices and pSliceDataOffsets
+   * will be set later */
+
+  params->ref_pic_flag = picture->ref;
+  /* will be updated later, if any slices belong to this frame is not
+   * intra slice */
+  params->intra_pic_flag = 1;
+
+  h264_params->frame_num = picture->frame_num;
+  h264_params->ref_pic_flag = picture->ref;
+  /* FIXME: should be updated depending on field type? */
+  h264_params->CurrFieldOrderCnt[0] = picture->top_field_order_cnt;
+  h264_params->CurrFieldOrderCnt[1] = picture->bottom_field_order_cnt;
+
+  if (!self->last_sps || self->last_sps != sps) {
+    GST_DEBUG_OBJECT (self, "Update params from SPS and PPS");
+    gst_nv_h264_dec_picture_params_from_sps (self,
+        sps, slice_header->field_pic_flag, h264_params);
+    gst_nv_h264_dec_picture_params_from_pps (self, pps, h264_params);
+    self->last_sps = sps;
+    self->last_pps = pps;
+  } else if (!self->last_pps || self->last_pps != pps) {
+    GST_DEBUG_OBJECT (self, "Update params from PPS");
+    gst_nv_h264_dec_picture_params_from_pps (self, pps, h264_params);
+    self->last_pps = pps;
+  } else {
+    GST_TRACE_OBJECT (self, "SPS and PPS were not updated");
+  }
+
+  memset (&h264_params->dpb, 0, sizeof (h264_params->dpb));
+  for (i = 0; i < G_N_ELEMENTS (h264_params->dpb); i++)
+    h264_params->dpb[i].PicIdx = -1;
+
+  dpb_array = gst_h264_dpb_get_pictures_all (dpb);
+  for (i = 0; i < dpb_array->len && i < G_N_ELEMENTS (h264_params->dpb); i++) {
+    GstH264Picture *other = g_array_index (dpb_array, GstH264Picture *, i);
+    GstNvDecoderFrame *other_frame;
+    gint picture_index = -1;
+    CUVIDH264DPBENTRY *dpb = &h264_params->dpb[i];
+
+    if (!other->ref)
+      continue;
+
+    other_frame = gst_nv_h264_dec_get_decoder_frame_from_picture (self, other);
+
+    if (other_frame)
+      picture_index = other_frame->index;
+
+    dpb->PicIdx = picture_index;
+    if (other->long_term) {
+      dpb->FrameIdx = other->long_term_frame_idx;
+      dpb->is_long_term = 1;
+    } else {
+      dpb->FrameIdx = other->frame_num;
+      dpb->is_long_term = 0;
+    }
+
+    dpb->not_existing = other->nonexisting;
+    if (dpb->not_existing && dpb->PicIdx != -1) {
+      GST_WARNING_OBJECT (self,
+          "Non-existing frame has valid picture index %d", dpb->PicIdx);
+      dpb->PicIdx = -1;
+    }
+
+    /* FIXME: 1=top_field, 2=bottom_field, 3=both_fields */
+    dpb->used_for_reference = 3;
+
+    dpb->FieldOrderCnt[0] = other->top_field_order_cnt;
+    dpb->FieldOrderCnt[1] = other->bottom_field_order_cnt;
+  }
+
+  g_array_unref (dpb_array);
+
+  return TRUE;
+}
+
+static gboolean
+gst_nv_h264_dec_decode_slice (GstH264Decoder * decoder,
+    GstH264Picture * picture, GstH264Slice * slice)
+{
+  GstNvH264Dec *self = GST_NV_H264_DEC (decoder);
+  gsize new_size;
+
+  GST_LOG_OBJECT (self, "Decode slice, nalu size %u", slice->nalu.size);
+
+  if (self->slice_offsets_alloc_len < self->num_slices + 1) {
+    self->slice_offsets = (guint *) g_realloc_n (self->slice_offsets,
+        self->num_slices + 1, sizeof (guint));
+  }
+  self->slice_offsets[self->num_slices] = self->bitstream_buffer_offset;
+  GST_LOG_OBJECT (self, "Slice offset %u for slice %d",
+      self->slice_offsets[self->num_slices], self->num_slices);
+
+  self->num_slices++;
+
+  new_size = self->bitstream_buffer_offset + slice->nalu.size + 3;
+  if (self->bitstream_buffer_alloc_size < new_size) {
+    self->bitstream_buffer =
+        (guint8 *) g_realloc (self->bitstream_buffer, new_size);
+  }
+
+  self->bitstream_buffer[self->bitstream_buffer_offset] = 0;
+  self->bitstream_buffer[self->bitstream_buffer_offset + 1] = 0;
+  self->bitstream_buffer[self->bitstream_buffer_offset + 2] = 1;
+
+  memcpy (self->bitstream_buffer + self->bitstream_buffer_offset + 3,
+      slice->nalu.data + slice->nalu.offset, slice->nalu.size);
+  self->bitstream_buffer_offset = new_size;
+
+  if (!GST_H264_IS_I_SLICE (&slice->header) &&
+      !GST_H264_IS_SI_SLICE (&slice->header))
+    self->params.intra_pic_flag = 0;
+
+  return TRUE;
+}
+
+static gboolean
+gst_nv_h264_dec_end_picture (GstH264Decoder * decoder, GstH264Picture * picture)
+{
+  GstNvH264Dec *self = GST_NV_H264_DEC (decoder);
+  gboolean ret;
+  CUVIDPICPARAMS *params = &self->params;
+
+  params->nBitstreamDataLen = self->bitstream_buffer_offset;
+  params->pBitstreamData = self->bitstream_buffer;
+  params->nNumSlices = self->num_slices;
+  params->pSliceDataOffsets = self->slice_offsets;
+
+  GST_LOG_OBJECT (self, "End picture, bitstream len: %" G_GSIZE_FORMAT
+      ", num slices %d", self->bitstream_buffer_offset, self->num_slices);
+
+  ret = gst_nv_decoder_decode_picture (self->decoder, &self->params);
+
+  if (!ret)
+    GST_ERROR_OBJECT (self, "Failed to decode picture");
+
+  return ret;
+}
+
+typedef struct
+{
+  GstCaps *sink_caps;
+  GstCaps *src_caps;
+  guint cuda_device_id;
+  gboolean is_default;
+} GstNvH264DecClassData;
+
+static void
+gst_nv_h264_dec_subclass_init (gpointer klass, GstNvH264DecClassData * cdata)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+  GstNvH264DecClass *nvdec_class = (GstNvH264DecClass *) (klass);
+  gchar *long_name;
+
+  if (cdata->is_default) {
+    long_name = g_strdup_printf ("NVDEC H.264 Stateless Decoder");
+  } else {
+    long_name = g_strdup_printf ("NVDEC H.264 Stateless Decoder with device %d",
+        cdata->cuda_device_id);
+  }
+
+  gst_element_class_set_metadata (element_class, long_name,
+      "Codec/Decoder/Video/Hardware",
+      "Nvidia H.264 video decoder", "Seungha Yang <seungha@centricular.com>");
+  g_free (long_name);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+          cdata->sink_caps));
+  gst_element_class_add_pad_template (element_class,
+      gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+          cdata->src_caps));
+
+  nvdec_class->cuda_device_id = cdata->cuda_device_id;
+
+  gst_caps_unref (cdata->sink_caps);
+  gst_caps_unref (cdata->src_caps);
+  g_free (cdata);
+}
+
+void
+gst_nv_h264_dec_register (GstPlugin * plugin, guint device_id, guint rank,
+    GstCaps * sink_caps, GstCaps * src_caps)
+{
+  GTypeQuery type_query;
+  GTypeInfo type_info = { 0, };
+  GType subtype;
+  gchar *type_name;
+  gchar *feature_name;
+  GstNvH264DecClassData *cdata;
+  gboolean is_default = TRUE;
+  const GValue *value;
+  GstStructure *s;
+
+  cdata = g_new0 (GstNvH264DecClassData, 1);
+  cdata->sink_caps = gst_caps_from_string ("video/x-h264, "
+      "stream-format= (string) { avc, avc3, byte-stream }, "
+      "alignment= (string) au, profile = (string) { high, main }, "
+      "framerate = " GST_VIDEO_FPS_RANGE);
+
+  s = gst_caps_get_structure (sink_caps, 0);
+  value = gst_structure_get_value (s, "width");
+  gst_caps_set_value (cdata->sink_caps, "width", value);
+
+  value = gst_structure_get_value (s, "height");
+  gst_caps_set_value (cdata->sink_caps, "height", value);
+
+  GST_MINI_OBJECT_FLAG_SET (cdata->sink_caps,
+      GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED);
+  cdata->src_caps = gst_caps_ref (src_caps);
+  cdata->cuda_device_id = device_id;
+
+  g_type_query (GST_TYPE_NV_H264_DEC, &type_query);
+  memset (&type_info, 0, sizeof (type_info));
+  type_info.class_size = type_query.class_size;
+  type_info.instance_size = type_query.instance_size;
+  type_info.class_init = (GClassInitFunc) gst_nv_h264_dec_subclass_init;
+  type_info.class_data = cdata;
+
+  type_name = g_strdup ("GstNvH264StatelessDec");
+  feature_name = g_strdup ("nvh264sldec");
+
+  if (g_type_from_name (type_name) != 0) {
+    g_free (type_name);
+    g_free (feature_name);
+    type_name = g_strdup_printf ("GstNvH264StatelessDevice%dDec", device_id);
+    feature_name = g_strdup_printf ("nvh264sldevice%ddec", device_id);
+
+    is_default = FALSE;
+  }
+
+  cdata->is_default = is_default;
+  subtype = g_type_register_static (GST_TYPE_NV_H264_DEC,
+      type_name, &type_info, 0);
+
+  /* make lower rank than default device */
+  if (rank > 0 && !is_default)
+    rank--;
+
+  if (!gst_element_register (plugin, feature_name, rank, subtype))
+    GST_WARNING ("Failed to register plugin '%s'", type_name);
+
+  g_free (type_name);
+  g_free (feature_name);
+}
diff --git a/sys/nvcodec/gstnvh264dec.h b/sys/nvcodec/gstnvh264dec.h
new file mode 100644 (file)
index 0000000..f29f776
--- /dev/null
@@ -0,0 +1,48 @@
+/* GStreamer
+ * Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_NV_H264_DEC_H__
+#define __GST_NV_H264_DEC_H__
+
+#include <gst/gst.h>
+#include <gst/codecs/gsth264decoder.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_NV_H264_DEC            (gst_nv_h264_dec_get_type())
+#define GST_NV_H264_DEC(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_NV_H264_DEC, GstNvH264Dec))
+#define GST_NV_H264_DEC_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass),  GST_TYPE_NV_H264_DEC, GstNvH264DecClass))
+#define GST_NV_H264_DEC_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj),  GST_TYPE_NV_H264_DEC, GstNvH264DecClass))
+
+typedef struct _GstNvH264Dec GstNvH264Dec;
+typedef struct _GstNvH264DecClass GstNvH264DecClass;
+
+G_GNUC_INTERNAL
+GType gst_nv_h264_dec_get_type (void);
+
+G_GNUC_INTERNAL
+void gst_nv_h264_dec_register (GstPlugin * plugin,
+                               guint device_id,
+                               guint rank,
+                               GstCaps * sink_caps,
+                               GstCaps * src_caps);
+
+G_END_DECLS
+
+#endif /* __GST_NV_H264_DEC_H__ */
index b8d46f6f9a6e78bc486b7b02d7a74647860420c2..5a75e8129d20a3f607ae067b5440712d0b0bbacd 100644 (file)
@@ -9,6 +9,8 @@ nvcodec_sources = [
   'gstcuvidloader.c',
   'gstcudacontext.c',
   'gstcudautils.c',
+  'gstnvdecoder.c',
+  'gstnvh264dec.c',
 ]
 
 if get_option('nvcodec').disabled()
@@ -16,7 +18,7 @@ if get_option('nvcodec').disabled()
 endif
 
 plugin_incdirs = [configinc, include_directories('./stub')]
-extra_c_args = []
+extra_c_args = ['-DGST_USE_UNSTABLE_API']
 
 if gstgl_dep.found()
   extra_c_args += ['-DHAVE_NVCODEC_GST_GL=1']
@@ -26,7 +28,7 @@ gstnvcodec = library('gstnvcodec',
   nvcodec_sources,
   c_args : gst_plugins_bad_args + extra_c_args,
   include_directories : plugin_incdirs,
-  dependencies : [gstbase_dep, gstvideo_dep, gstpbutils_dep, gstgl_dep, gmodule_dep],
+  dependencies : [gstbase_dep, gstvideo_dep, gstpbutils_dep, gstgl_dep, gmodule_dep, gstcodecs_dep],
   install : true,
   install_dir : plugins_install_dir,
 )
index 7a75dc67d0569966b47f20c32b2f2e27f1b2e710..1d813c0188dfb40e04e8f037f1c463ba7a789646 100644 (file)
 
 #include "gstnvdec.h"
 #include "gstnvenc.h"
+#include "gstnvh264dec.h"
+#include "gstnvdecoder.h"
 
 GST_DEBUG_CATEGORY (gst_nvcodec_debug);
 GST_DEBUG_CATEGORY (gst_nvdec_debug);
 GST_DEBUG_CATEGORY (gst_nvenc_debug);
+GST_DEBUG_CATEGORY (gst_nv_decoder_debug);
 
 #define GST_CAT_DEFAULT gst_nvcodec_debug
 
@@ -53,6 +56,7 @@ plugin_init (GstPlugin * plugin)
   GST_DEBUG_CATEGORY_INIT (gst_nvcodec_debug, "nvcodec", 0, "nvcodec");
   GST_DEBUG_CATEGORY_INIT (gst_nvdec_debug, "nvdec", 0, "nvdec");
   GST_DEBUG_CATEGORY_INIT (gst_nvenc_debug, "nvenc", 0, "nvenc");
+  GST_DEBUG_CATEGORY_INIT (gst_nv_decoder_debug, "nvdecoder", 0, "nvdecoder");
 
   if (!gst_cuda_load_library ()) {
     GST_WARNING ("Failed to load cuda library");
@@ -104,8 +108,35 @@ plugin_init (GstPlugin * plugin)
 
     CuCtxPopCurrent (NULL);
 
-    if (nvdec_available)
-      gst_nvdec_plugin_init (plugin, i, cuda_ctx);
+    if (nvdec_available) {
+      gint j;
+
+      for (j = 0; j < cudaVideoCodec_NumCodecs; j++) {
+        GstCaps *sink_template = NULL;
+        GstCaps *src_template = NULL;
+        cudaVideoCodec codec = (cudaVideoCodec) j;
+
+        if (gst_nv_decoder_check_device_caps (cuda_ctx,
+                codec, &sink_template, &src_template)) {
+          const gchar *codec_name = gst_cuda_video_codec_to_string (codec);
+
+          GST_INFO ("CUDA video codec %s, sink template %" GST_PTR_FORMAT
+              "src template %" GST_PTR_FORMAT, codec_name,
+              sink_template, src_template);
+
+          gst_nvdec_plugin_init (plugin,
+              i, codec, codec_name, sink_template, src_template);
+
+          if (codec == cudaVideoCodec_H264) {
+            gst_nv_h264_dec_register (plugin,
+                i, GST_RANK_SECONDARY, sink_template, src_template);
+          }
+
+          gst_caps_unref (sink_template);
+          gst_caps_unref (src_template);
+        }
+      }
+    }
 
     if (nvenc_available)
       gst_nvenc_plugin_init (plugin, i, cuda_ctx);