| /* |
| * 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 <gst/gl/gstglfuncs.h> |
| #include <cudaGL.h> |
| |
| typedef enum |
| { |
| GST_NVDEC_QUEUE_ITEM_TYPE_SEQUENCE, |
| GST_NVDEC_QUEUE_ITEM_TYPE_DECODE, |
| GST_NVDEC_QUEUE_ITEM_TYPE_DISPLAY |
| } GstNvDecQueueItemType; |
| |
| typedef struct _GstNvDecQueueItem |
| { |
| GstNvDecQueueItemType type; |
| gpointer data; |
| } GstNvDecQueueItem; |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_nvdec_debug_category); |
| #define GST_CAT_DEFAULT gst_nvdec_debug_category |
| |
| static inline gboolean |
| cuda_OK (CUresult result) |
| { |
| const gchar *error_name, *error_text; |
| |
| if (result != CUDA_SUCCESS) { |
| cuGetErrorName (result, &error_name); |
| cuGetErrorString (result, &error_text); |
| GST_WARNING ("CUDA call failed: %s, %s", error_name, error_text); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| G_DEFINE_TYPE (GstNvDecCudaContext, gst_nvdec_cuda_context, G_TYPE_OBJECT); |
| |
| static void |
| gst_nvdec_cuda_context_finalize (GObject * object) |
| { |
| GstNvDecCudaContext *self = (GstNvDecCudaContext *) object; |
| |
| if (self->lock) { |
| GST_DEBUG ("destroying CUDA context lock"); |
| if (cuda_OK (cuvidCtxLockDestroy (self->lock))) |
| self->lock = NULL; |
| else |
| GST_ERROR ("failed to destroy CUDA context lock"); |
| } |
| |
| if (self->context) { |
| GST_DEBUG ("destroying CUDA context"); |
| if (cuda_OK (cuCtxDestroy (self->context))) |
| self->context = NULL; |
| else |
| GST_ERROR ("failed to destroy CUDA context"); |
| } |
| |
| G_OBJECT_CLASS (gst_nvdec_cuda_context_parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_nvdec_cuda_context_class_init (GstNvDecCudaContextClass * klass) |
| { |
| G_OBJECT_CLASS (klass)->finalize = gst_nvdec_cuda_context_finalize; |
| } |
| |
| static void |
| gst_nvdec_cuda_context_init (GstNvDecCudaContext * self) |
| { |
| if (!cuda_OK (cuInit (0))) |
| GST_ERROR ("failed to init CUDA"); |
| |
| if (!cuda_OK (cuCtxCreate (&self->context, CU_CTX_SCHED_AUTO, 0))) |
| GST_ERROR ("failed to create CUDA context"); |
| |
| if (!cuda_OK (cuCtxPopCurrent (NULL))) |
| GST_ERROR ("failed to pop current CUDA context"); |
| |
| if (!cuda_OK (cuvidCtxLockCreate (&self->lock, self->context))) |
| GST_ERROR ("failed to create CUDA context lock"); |
| } |
| |
| typedef struct _GstNvDecCudaGraphicsResourceInfo |
| { |
| GstGLContext *gl_context; |
| GstNvDecCudaContext *cuda_context; |
| CUgraphicsResource resource; |
| } GstNvDecCudaGraphicsResourceInfo; |
| |
| static void |
| register_cuda_resource (GstGLContext * context, gpointer * args) |
| { |
| GstMemory *mem = GST_MEMORY_CAST (args[0]); |
| GstNvDecCudaGraphicsResourceInfo *cgr_info = |
| (GstNvDecCudaGraphicsResourceInfo *) args[1]; |
| GstMapInfo map_info = GST_MAP_INFO_INIT; |
| guint texture_id; |
| |
| if (!cuda_OK (cuvidCtxLock (cgr_info->cuda_context->lock, 0))) |
| GST_WARNING ("failed to lock CUDA context"); |
| |
| if (gst_memory_map (mem, &map_info, GST_MAP_READ | GST_MAP_GL)) { |
| texture_id = *(guint *) map_info.data; |
| |
| if (!cuda_OK (cuGraphicsGLRegisterImage (&cgr_info->resource, 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"); |
| |
| if (!cuda_OK (cuvidCtxUnlock (cgr_info->cuda_context->lock, 0))) |
| GST_WARNING ("failed to unlock CUDA context"); |
| } |
| |
| static void |
| unregister_cuda_resource (GstGLContext * context, |
| GstNvDecCudaGraphicsResourceInfo * cgr_info) |
| { |
| if (!cuda_OK (cuvidCtxLock (cgr_info->cuda_context->lock, 0))) |
| GST_WARNING ("failed to lock CUDA context"); |
| |
| if (!cuda_OK (cuGraphicsUnregisterResource ((const CUgraphicsResource) |
| cgr_info->resource))) |
| GST_WARNING ("failed to unregister resource"); |
| |
| if (!cuda_OK (cuvidCtxUnlock (cgr_info->cuda_context->lock, 0))) |
| GST_WARNING ("failed to unlock CUDA context"); |
| } |
| |
| static void |
| free_cgr_info (GstNvDecCudaGraphicsResourceInfo * cgr_info) |
| { |
| gst_gl_context_thread_add (cgr_info->gl_context, |
| (GstGLContextThreadFunc) unregister_cuda_resource, cgr_info); |
| gst_object_unref (cgr_info->gl_context); |
| g_object_unref (cgr_info->cuda_context); |
| g_slice_free (GstNvDecCudaGraphicsResourceInfo, cgr_info); |
| } |
| |
| static CUgraphicsResource |
| ensure_cuda_graphics_resource (GstMemory * mem, |
| GstNvDecCudaContext * cuda_context) |
| { |
| static GQuark quark = 0; |
| GstNvDecCudaGraphicsResourceInfo *cgr_info; |
| gpointer args[2]; |
| |
| if (!gst_is_gl_base_memory (mem)) { |
| GST_WARNING ("memory is not GL base memory"); |
| return NULL; |
| } |
| |
| if (!quark) |
| quark = g_quark_from_static_string ("GstNvDecCudaGraphicsResourceInfo"); |
| |
| cgr_info = gst_mini_object_get_qdata (GST_MINI_OBJECT (mem), quark); |
| if (!cgr_info) { |
| cgr_info = g_slice_new (GstNvDecCudaGraphicsResourceInfo); |
| cgr_info->gl_context = |
| gst_object_ref (GST_GL_BASE_MEMORY_CAST (mem)->context); |
| cgr_info->cuda_context = g_object_ref (cuda_context); |
| args[0] = mem; |
| args[1] = cgr_info; |
| gst_gl_context_thread_add (cgr_info->gl_context, |
| (GstGLContextThreadFunc) register_cuda_resource, args); |
| gst_mini_object_set_qdata (GST_MINI_OBJECT (mem), quark, cgr_info, |
| (GDestroyNotify) free_cgr_info); |
| } |
| |
| return cgr_info->resource; |
| } |
| |
| 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 gboolean gst_nvdec_flush (GstVideoDecoder * decoder); |
| static GstFlowReturn gst_nvdec_drain (GstVideoDecoder * decoder); |
| |
| 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); |
| video_decoder_class->drain = GST_DEBUG_FUNCPTR (gst_nvdec_drain); |
| video_decoder_class->flush = GST_DEBUG_FUNCPTR (gst_nvdec_flush); |
| |
| 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]; |
| CUgraphicsResource *resources = (CUgraphicsResource *) args[2]; |
| guint num_resources = GPOINTER_TO_UINT (args[3]); |
| 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 (num_resources, 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 < num_resources; i++) { |
| if (!cuda_OK (cuGraphicsSubResourceGetMappedArray (&array, 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 (num_resources, 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, num_resources; |
| CUVIDPICPARAMS *decode_params; |
| CUVIDPARSERDISPINFO *dispinfo; |
| CUgraphicsResource *resources; |
| gpointer args[4]; |
| 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; |
| } |
| |
| num_resources = gst_buffer_n_memory (pending_frame->output_buffer); |
| resources = g_new (CUgraphicsResource, num_resources); |
| |
| for (i = 0; i < num_resources; i++) { |
| mem = gst_buffer_get_memory (pending_frame->output_buffer, i); |
| resources[i] = |
| ensure_cuda_graphics_resource (mem, nvdec->cuda_context); |
| GST_MINI_OBJECT_FLAG_SET (mem, |
| GST_GL_BASE_MEMORY_TRANSFER_NEED_DOWNLOAD); |
| gst_memory_unref (mem); |
| } |
| |
| args[0] = nvdec; |
| args[1] = dispinfo; |
| args[2] = resources; |
| args[3] = GUINT_TO_POINTER (num_resources); |
| gst_gl_context_thread_add (nvdec->gl_context, |
| (GstGLContextThreadFunc) copy_video_frame_to_gl_textures, args); |
| g_free (resources); |
| |
| 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_flush (GstVideoDecoder * decoder) |
| { |
| GstNvDec *nvdec = GST_NVDEC (decoder); |
| CUVIDSOURCEDATAPACKET packet = { 0, }; |
| |
| GST_DEBUG_OBJECT (nvdec, "flush"); |
| |
| packet.payload_size = 0; |
| packet.payload = NULL; |
| packet.flags = CUVID_PKT_ENDOFSTREAM; |
| |
| if (!cuda_OK (cuvidParseVideoData (nvdec->parser, &packet))) |
| GST_WARNING_OBJECT (nvdec, "parser failed"); |
| |
| handle_pending_frames (nvdec); |
| |
| return TRUE; |
| } |
| |
| static GstFlowReturn |
| gst_nvdec_drain (GstVideoDecoder * decoder) |
| { |
| GstNvDec *nvdec = GST_NVDEC (decoder); |
| CUVIDSOURCEDATAPACKET packet = { 0, }; |
| |
| GST_DEBUG_OBJECT (nvdec, "draining decoder"); |
| |
| packet.payload_size = 0; |
| packet.payload = NULL; |
| packet.flags = CUVID_PKT_ENDOFSTREAM; |
| |
| if (!cuda_OK (cuvidParseVideoData (nvdec->parser, &packet))) |
| GST_WARNING_OBJECT (nvdec, "parser failed"); |
| |
| 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); |
| } |