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