Commit b83cbd9f authored by Seungha Yang's avatar Seungha Yang 🐑
Browse files

nvcodec: Introduce NVIDA CUDA helpers

New object and helper functions can remove duplicated code
from nvenc/nvdec. Also this is prework for CUDA device context sharing
among nvdec(s)/nvenc(s).
parent e87bd30f
......@@ -3,6 +3,8 @@ plugin_LTLIBRARIES = libgstnvcodec.la
libgstnvcodec_la_SOURCES = \
plugin.c \
gstcudaloader.c
gstcudacontext.c \
gstcudautils.c
if USE_NVDEC
libgstnvcodec_la_SOURCES += \
......@@ -26,6 +28,8 @@ noinst_HEADERS = \
gstnvh265enc.h \
gstcudaloader.h \
gstcuvidloader.h
gstcudacontext.h \
gstcudautils.h
libgstnvcodec_la_CFLAGS = \
$(GST_PLUGINS_BAD_CFLAGS) \
......
/* GStreamer
* Copyright (C) <2018-2019> Seungha Yang <seungha.yang@navercorp.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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstcudaloader.h"
#include "gstcudacontext.h"
#include "gstcudautils.h"
GST_DEBUG_CATEGORY_STATIC (gst_cuda_context_debug);
#define GST_CAT_DEFAULT gst_cuda_context_debug
enum
{
PROP_0,
PROP_DEVICE_ID
};
#define DEFAULT_DEVICE_ID -1
struct _GstCudaContextPrivate
{
CUcontext context;
CUdevice device;
gint device_id;
};
#define gst_cuda_context_parent_class parent_class
G_DEFINE_TYPE_WITH_PRIVATE (GstCudaContext, gst_cuda_context, GST_TYPE_OBJECT);
static void gst_cuda_context_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_cuda_context_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static void gst_cuda_context_constructed (GObject * object);
static void gst_cuda_context_finalize (GObject * object);
static void
gst_cuda_context_class_init (GstCudaContextClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->set_property = gst_cuda_context_set_property;
gobject_class->get_property = gst_cuda_context_get_property;
gobject_class->constructed = gst_cuda_context_constructed;
gobject_class->finalize = gst_cuda_context_finalize;
g_object_class_install_property (gobject_class, PROP_DEVICE_ID,
g_param_spec_int ("cuda-device-id", "Cuda Device ID",
"Set the GPU device to use for operations (-1 = auto)",
-1, G_MAXINT, DEFAULT_DEVICE_ID,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
GST_DEBUG_CATEGORY_INIT (gst_cuda_context_debug,
"cudacontext", 0, "CUDA Context");
}
static void
gst_cuda_context_init (GstCudaContext * context)
{
GstCudaContextPrivate *priv = gst_cuda_context_get_instance_private (context);
priv->context = NULL;
priv->device_id = DEFAULT_DEVICE_ID;
context->priv = priv;
}
static void
gst_cuda_context_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstCudaContext *context = GST_CUDA_CONTEXT (object);
GstCudaContextPrivate *priv = context->priv;
switch (prop_id) {
case PROP_DEVICE_ID:
priv->device_id = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_cuda_context_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstCudaContext *context = GST_CUDA_CONTEXT (object);
GstCudaContextPrivate *priv = context->priv;
switch (prop_id) {
case PROP_DEVICE_ID:
g_value_set_int (value, priv->device_id);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_cuda_context_constructed (GObject * object)
{
static volatile gsize once = 0;
GstCudaContext *context = GST_CUDA_CONTEXT (object);
GstCudaContextPrivate *priv = context->priv;
CUcontext cuda_ctx, old_ctx;
gboolean ret = TRUE;
CUdevice cdev = 0, cuda_dev = -1;
int dev_count = 0;
char name[256];
int min = 0, maj = 0;
int i;
if (g_once_init_enter (&once)) {
if (CuInit (0) != CUDA_SUCCESS) {
GST_ERROR_OBJECT (context, "Failed to cuInit");
ret = FALSE;
}
g_once_init_leave (&once, ret);
if (!ret)
return;
}
if (!gst_cuda_result (CuDeviceGetCount (&dev_count)) || dev_count == 0) {
GST_WARNING ("No CUDA devices detected");
return;
}
for (i = 0; i < dev_count; ++i) {
if (gst_cuda_result (CuDeviceGet (&cdev, i)) &&
gst_cuda_result (CuDeviceGetName (name, sizeof (name), cdev)) &&
gst_cuda_result (CuDeviceGetAttribute (&maj,
CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, cdev)) &&
gst_cuda_result (CuDeviceGetAttribute (&min,
CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, cdev))) {
GST_INFO ("GPU #%d supports NVENC: %s (%s) (Compute SM %d.%d)", i,
(((maj << 4) + min) >= 0x30) ? "yes" : "no", name, maj, min);
if (priv->device_id == -1 || priv->device_id == cdev) {
priv->device_id = cuda_dev = cdev;
}
}
}
if (cuda_dev == -1) {
GST_WARNING ("Device with id %d does not exist or does not support NVENC",
priv->device_id);
return;
}
GST_DEBUG ("Creating cuda context for device index %d", cuda_dev);
if (!gst_cuda_result (CuCtxCreate (&cuda_ctx, 0, cuda_dev))) {
GST_WARNING ("Failed to create CUDA context for cuda device %d", cuda_dev);
return;
}
if (!gst_cuda_result (CuCtxPopCurrent (&old_ctx))) {
return;
}
GST_INFO ("Created CUDA context %p with device-id %d", cuda_ctx, cuda_dev);
priv->context = cuda_ctx;
priv->device = cuda_dev;
}
static void
gst_cuda_context_finalize (GObject * object)
{
GstCudaContext *context = GST_CUDA_CONTEXT_CAST (object);
GstCudaContextPrivate *priv = context->priv;
if (priv->context) {
GST_DEBUG_OBJECT (context, "Destroying CUDA context %p", priv->context);
gst_cuda_result (CuCtxDestroy (priv->context));
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/**
* gst_cuda_context_new:
* @device_id: device-id for creating #GstCudaContext
*
* Create #GstCudaContext with given device_id
*
* Returns: a new #GstCudaContext or %NULL on failure
*/
GstCudaContext *
gst_cuda_context_new (gint device_id)
{
GstCudaContext *self =
g_object_new (GST_TYPE_CUDA_CONTEXT, "cuda-device-id", device_id, NULL);
gst_object_ref_sink (self);
if (!self->priv->context) {
GST_ERROR ("Failed to create CUDA context");
gst_clear_object (&self);
}
return self;
}
/**
* gst_cuda_context_push:
* @ctx: a #GstCudaContext to push current thread
*
* Pushes the given @ctx onto the CPU thread's stack of current contexts.
* The specified context becomes the CPU thread's current context,
* so all CUDA functions that operate on the current context are affected.
*
* Returns: %TRUE if @ctx was pushed without error.
*/
gboolean
gst_cuda_context_push (GstCudaContext * ctx)
{
g_return_val_if_fail (ctx, FALSE);
g_return_val_if_fail (GST_IS_CUDA_CONTEXT (ctx), FALSE);
return gst_cuda_result (CuCtxPushCurrent (ctx->priv->context));
}
/**
* gst_cuda_context_pop:
*
* Pops the current CUDA context from CPU thread
*
* Returns: %TRUE if @ctx was pushed without error.
*/
gboolean
gst_cuda_context_pop (void)
{
CUcontext dummy;
return gst_cuda_result (CuCtxPopCurrent (&dummy));
}
/**
* gst_cuda_context_get_context:
* @ctx: a #GstCudaContext
*
* Get CUDA device context. Caller must not modify and/or destroy
* returned device context.
*
* Returns: the #CUcontext of @ctx
*/
gpointer
gst_cuda_context_get_context (GstCudaContext * ctx)
{
g_return_val_if_fail (ctx, NULL);
g_return_val_if_fail (GST_IS_CUDA_CONTEXT (ctx), NULL);
return ctx->priv->context;
}
/* GStreamer
* Copyright (C) <2018-2019> Seungha Yang <seungha.yang@navercorp.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_CUDA_CONTEXT_H__
#define __GST_CUDA_CONTEXT_H__
#include <gst/gst.h>
G_BEGIN_DECLS
#define GST_TYPE_CUDA_CONTEXT (gst_cuda_context_get_type())
#define GST_CUDA_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_CUDA_CONTEXT,GstCudaContext))
#define GST_CUDA_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_CUDA_CONTEXT,GstCudaContextClass))
#define GST_CUDA_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_CUDA_CONTEXT,GstCudaContextClass))
#define GST_IS_CUDA_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),GST_TYPE_CUDA_CONTEXT))
#define GST_IS_CUDA_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CUDA_CONTEXT))
#define GST_CUDA_CONTEXT_CAST(obj) ((GstCudaContext*)(obj))
#define GST_CUDA_CONTEXT_TYPE "gst.cuda.context"
typedef struct _GstCudaContext GstCudaContext;
typedef struct _GstCudaContextClass GstCudaContextClass;
typedef struct _GstCudaContextPrivate GstCudaContextPrivate;
/*
* GstCudaContext:
*/
struct _GstCudaContext
{
GstObject object;
/*< private >*/
GstCudaContextPrivate *priv;
};
/*
* GstCudaContextClass:
*/
struct _GstCudaContextClass
{
GstObjectClass parent_class;
};
GType gst_cuda_context_get_type (void);
GstCudaContext * gst_cuda_context_new (gint device_id);
gboolean gst_cuda_context_push (GstCudaContext * ctx);
gboolean gst_cuda_context_pop (void);
gpointer gst_cuda_context_get_context (GstCudaContext * ctx);
G_END_DECLS
#endif /* __GST_CUDA_CONTEXT_H__ */
/* GStreamer
* Copyright (C) <2018-2019> Seungha Yang <seungha.yang@navercorp.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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstcudaloader.h"
#include "gstcudautils.h"
#include "gstcudacontext.h"
GST_DEBUG_CATEGORY_STATIC (gst_cuda_utils_debug);
#define GST_CAT_DEFAULT gst_cuda_utils_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_CONTEXT);
static void
_init_debug (void)
{
static volatile gsize once_init = 0;
if (g_once_init_enter (&once_init)) {
GST_DEBUG_CATEGORY_INIT (gst_cuda_utils_debug, "cudautils", 0,
"CUDA utils");
g_once_init_leave (&once_init, 1);
}
}
static void
_init_context_debug (void)
{
static volatile gsize _init = 0;
if (g_once_init_enter (&_init)) {
GST_DEBUG_CATEGORY_GET (GST_CAT_CONTEXT, "GST_CONTEXT");
g_once_init_leave (&_init, 1);
}
}
/**
* gst_cuda_result:
* @result: CUDA device API return code #CUresult
*
* Returns: %TRUE if CUDA device API call result is CUDA_SUCCESS
*/
gboolean
gst_cuda_result (gint result)
{
const gchar *error_name, *error_text;
_init_debug ();
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;
}
static gboolean
pad_query (const GValue * item, GValue * value, gpointer user_data)
{
GstPad *pad = g_value_get_object (item);
GstQuery *query = user_data;
gboolean res;
res = gst_pad_peer_query (pad, query);
if (res) {
g_value_set_boolean (value, TRUE);
return FALSE;
}
GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, pad, "pad peer query failed");
return TRUE;
}
static gboolean
run_query (GstElement * element, GstQuery * query, GstPadDirection direction)
{
GstIterator *it;
GstIteratorFoldFunction func = pad_query;
GValue res = { 0 };
g_value_init (&res, G_TYPE_BOOLEAN);
g_value_set_boolean (&res, FALSE);
/* Ask neighbor */
if (direction == GST_PAD_SRC)
it = gst_element_iterate_src_pads (element);
else
it = gst_element_iterate_sink_pads (element);
while (gst_iterator_fold (it, func, &res, query) == GST_ITERATOR_RESYNC)
gst_iterator_resync (it);
gst_iterator_free (it);
return g_value_get_boolean (&res);
}
static void
find_cuda_context (GstElement * element)
{
GstQuery *query;
GstContext *ctxt;
/* 1) Query downstream with GST_QUERY_CONTEXT for the context and
* check if upstream already has a context of the specific type
* 2) Query upstream as above.
*/
query = gst_query_new_context (GST_CUDA_CONTEXT_TYPE);
if (run_query (element, query, GST_PAD_SRC)) {
gst_query_parse_context (query, &ctxt);
GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element,
"found context (%p) in downstream query", ctxt);
gst_element_set_context (element, ctxt);
} else if (run_query (element, query, GST_PAD_SINK)) {
gst_query_parse_context (query, &ctxt);
GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element,
"found context (%p) in upstream query", ctxt);
gst_element_set_context (element, ctxt);
} else {
/* 3) Post a GST_MESSAGE_NEED_CONTEXT message on the bus with
* the required context type and afterwards check if a
* usable context was set now. The message could
* be handled by the parent bins of the element and the
* application.
*/
GstMessage *msg;
GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element,
"posting need context message");
msg = gst_message_new_need_context (GST_OBJECT_CAST (element),
GST_CUDA_CONTEXT_TYPE);
gst_element_post_message (element, msg);
}
/*
* Whomever responds to the need-context message performs a
* GstElement::set_context() with the required context in which the element
* is required to update the cuda_ctx or call gst_cuda_handle_set_context().
*/
gst_query_unref (query);
}
static void
context_set_cuda_context (GstContext * context, GstCudaContext * cuda_ctx)
{
GstStructure *s;
g_return_if_fail (context != NULL);
if (cuda_ctx)
GST_CAT_LOG (GST_CAT_CONTEXT,
"setting GstCudaContext(%" GST_PTR_FORMAT ") on context(%"
GST_PTR_FORMAT ")", cuda_ctx, context);
s = gst_context_writable_structure (context);
gst_structure_set (s, GST_CUDA_CONTEXT_TYPE, GST_TYPE_CUDA_CONTEXT,
cuda_ctx, NULL);
}
/**
* gst_cuda_ensure_element_context:
* @element: the #GstElement running the query
* @cuda_ctx: (inout): the resulting #GstCudaContext
* @device_id: prefered device-id, pass device_id >=0 when
* the device_id explicitly required. Otherwise, set -1.
*
* Perform the steps necessary for retrieving a #GstCudaContext from the
* surrounding elements or from the application using the #GstContext mechanism.
*
* If the content of @cuda_ctx is not %NULL, then no #GstContext query is
* necessary for #GstCudaContext.
*
* Returns: whether a #GstCudaContext exists in @cuda_ctx
*/
gboolean
gst_cuda_ensure_element_context (GstElement * element,
GstCudaContext ** cuda_ctx, gint device_id)
{
GstCudaContext *ctx;
g_return_val_if_fail (element != NULL, FALSE);
g_return_val_if_fail (cuda_ctx != NULL, FALSE);
_init_context_debug ();
ctx = *cuda_ctx;
if (ctx)
return TRUE;
find_cuda_context (element);
if (*cuda_ctx)
return TRUE;
/* No available CUDA context in pipeline, create new one here */
*cuda_ctx = gst_cuda_context_new (device_id);
if (*cuda_ctx == NULL) {
GST_CAT_ERROR_OBJECT (GST_CAT_CONTEXT, element,
"Failed to create CUDA context with device-id %d", device_id);
return FALSE;
} else {
GstContext *context;
GstMessage *msg;
/* Propagate new CUDA context */
context = gst_context_new (GST_CUDA_CONTEXT_TYPE, TRUE);
context_set_cuda_context (context, *cuda_ctx);
gst_element_set_context (element, context);
GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element,
"posting have context (%p) message with CUDA context (%p)",
context, *cuda_ctx);
msg = gst_message_new_have_context (GST_OBJECT_CAST (element), context);
gst_element_post_message (GST_ELEMENT_CAST (element), msg);
}
return TRUE;
}
/**
* gst_cuda_handle_set_context:
* @element: a #GstElement
* @context: a #GstContext
* @cuda_ctx: (inout) (transfer full): location of a #GstCudaContext
* @device_id: prefered device-id, pass device_id >=0 when
* the device_id explicitly required. Otherwise, set -1.
*
* Helper function for implementing #GstElementClass.set_context() in
* CUDA capable elements.
*
* Retrieves the #GstCudaContext in @context and places the result in @cuda_ctx.
*