gstreamer/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.cpp
Seungha Yang 315cfaf2d8 nvencoder: Fix negotiation error when interlace-mode is unspecified
Use GST_PAD_SET_ACCEPT_INTERSECT() to accept caps without interlace-mode
field

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5229>
2023-08-24 14:58:25 +00:00

2435 lines
70 KiB
C++

/* GStreamer
* Copyright (C) 2022 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstnvencoder.h"
#include <gst/cuda/gstcudautils.h>
#include <gst/cuda/gstcudamemory.h>
#include <gst/cuda/gstcudabufferpool.h>
#include <gst/cuda/gstcudastream.h>
#include <gst/cuda/gstcuda-private.h>
#include <gst/base/gstbytewriter.h>
#include <string.h>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <memory>
#include <atomic>
#include "gstnvencobject.h"
#ifdef G_OS_WIN32
#include <wrl.h>
/* *INDENT-OFF* */
using namespace Microsoft::WRL;
/* *INDENT-ON* */
#endif
#ifdef HAVE_CUDA_GST_GL
#define SUPPORTED_GL_APIS GST_GL_API_OPENGL3
#endif
GST_DEBUG_CATEGORY (gst_nv_encoder_debug);
#define GST_CAT_DEFAULT gst_nv_encoder_debug
#define GST_NVENC_STATUS_FORMAT "s (%d)"
#define GST_NVENC_STATUS_ARGS(s) nvenc_status_to_string (s), s
enum
{
PROP_0,
PROP_CC_INSERT,
};
#define DEFAULT_CC_INSERT GST_NV_ENCODER_SEI_INSERT
struct _GstNvEncoderPrivate
{
_GstNvEncoderPrivate ()
{
memset (&init_params, 0, sizeof (NV_ENC_INITIALIZE_PARAMS));
memset (&config, 0, sizeof (NV_ENC_CONFIG));
}
GstCudaContext *context = nullptr;
GstCudaStream *stream = nullptr;
#ifdef G_OS_WIN32
GstD3D11Device *device = nullptr;
GstD3D11Fence *fence = nullptr;
#endif
#ifdef HAVE_CUDA_GST_GL
GstGLDisplay *gl_display = nullptr;
GstGLContext *gl_context = nullptr;
GstGLContext *other_gl_context = nullptr;
gboolean gl_interop = FALSE;
#endif
std::shared_ptr < GstNvEncObject > object;
GstNvEncoderDeviceMode subclass_device_mode;
GstNvEncoderDeviceMode selected_device_mode;
gint64 dxgi_adapter_luid = 0;
guint cuda_device_id = 0;
NV_ENC_INITIALIZE_PARAMS init_params;
NV_ENC_CONFIG config;
GstVideoCodecState *input_state = nullptr;
GstBufferPool *internal_pool = nullptr;
GstClockTime dts_offset = 0;
std::mutex lock;
std::condition_variable cond;
std::recursive_mutex context_lock;
std::unique_ptr < std::thread > encoding_thread;
std::atomic < GstFlowReturn > last_flow;
/* properties */
GstNvEncoderSeiInsertMode cc_insert = DEFAULT_CC_INSERT;
};
/**
* GstNvEncoder:
*
* Since: 1.22
*/
#define gst_nv_encoder_parent_class parent_class
G_DEFINE_ABSTRACT_TYPE (GstNvEncoder, gst_nv_encoder, GST_TYPE_VIDEO_ENCODER);
static void gst_nv_encoder_finalize (GObject * object);
static void gst_nv_encoder_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_nv_encoder_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static void gst_nv_encoder_set_context (GstElement * element,
GstContext * context);
static gboolean gst_nv_encoder_open (GstVideoEncoder * encoder);
static gboolean gst_nv_encoder_close (GstVideoEncoder * encoder);
static gboolean gst_nv_encoder_stop (GstVideoEncoder * encoder);
static gboolean gst_nv_encoder_sink_event (GstVideoEncoder * encoder,
GstEvent * event);
static gboolean gst_nv_encoder_sink_query (GstVideoEncoder * encoder,
GstQuery * query);
static gboolean gst_nv_encoder_src_query (GstVideoEncoder * encoder,
GstQuery * query);
static gboolean gst_nv_encoder_propose_allocation (GstVideoEncoder *
encoder, GstQuery * query);
static gboolean gst_nv_encoder_set_format (GstVideoEncoder * encoder,
GstVideoCodecState * state);
static GstFlowReturn gst_nv_encoder_handle_frame (GstVideoEncoder *
encoder, GstVideoCodecFrame * frame);
static GstFlowReturn gst_nv_encoder_finish (GstVideoEncoder * encoder);
static gboolean gst_nv_encoder_flush (GstVideoEncoder * encoder);
static gboolean gst_nv_encoder_transform_meta (GstVideoEncoder * encoder,
GstVideoCodecFrame * frame, GstMeta * meta);
static void
gst_nv_encoder_class_init (GstNvEncoderClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstVideoEncoderClass *videoenc_class = GST_VIDEO_ENCODER_CLASS (klass);
object_class->finalize = gst_nv_encoder_finalize;
object_class->set_property = gst_nv_encoder_set_property;
object_class->get_property = gst_nv_encoder_get_property;
/**
* GstNvEncoder:cc-insert:
*
* Closed Caption insert mode
*
* Since: 1.24
*/
g_object_class_install_property (object_class, PROP_CC_INSERT,
g_param_spec_enum ("cc-insert", "Closed Caption Insert",
"Closed Caption Insert mode",
GST_TYPE_NV_ENCODER_SEI_INSERT_MODE, DEFAULT_CC_INSERT,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
element_class->set_context = GST_DEBUG_FUNCPTR (gst_nv_encoder_set_context);
videoenc_class->open = GST_DEBUG_FUNCPTR (gst_nv_encoder_open);
videoenc_class->close = GST_DEBUG_FUNCPTR (gst_nv_encoder_close);
videoenc_class->stop = GST_DEBUG_FUNCPTR (gst_nv_encoder_stop);
videoenc_class->sink_event = GST_DEBUG_FUNCPTR (gst_nv_encoder_sink_event);
videoenc_class->sink_query = GST_DEBUG_FUNCPTR (gst_nv_encoder_sink_query);
videoenc_class->src_query = GST_DEBUG_FUNCPTR (gst_nv_encoder_src_query);
videoenc_class->propose_allocation =
GST_DEBUG_FUNCPTR (gst_nv_encoder_propose_allocation);
videoenc_class->set_format = GST_DEBUG_FUNCPTR (gst_nv_encoder_set_format);
videoenc_class->handle_frame =
GST_DEBUG_FUNCPTR (gst_nv_encoder_handle_frame);
videoenc_class->finish = GST_DEBUG_FUNCPTR (gst_nv_encoder_finish);
videoenc_class->flush = GST_DEBUG_FUNCPTR (gst_nv_encoder_flush);
videoenc_class->transform_meta =
GST_DEBUG_FUNCPTR (gst_nv_encoder_transform_meta);
GST_DEBUG_CATEGORY_INIT (gst_nv_encoder_debug, "nvencoder", 0, "nvencoder");
gst_type_mark_as_plugin_api (GST_TYPE_NV_ENCODER, (GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_NV_ENCODER_PRESET,
(GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_NV_ENCODER_RC_MODE,
(GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_NV_ENCODER_SEI_INSERT_MODE,
(GstPluginAPIFlags) 0);
}
static void
gst_nv_encoder_init (GstNvEncoder * self)
{
self->priv = new GstNvEncoderPrivate ();
gst_video_encoder_set_min_pts (GST_VIDEO_ENCODER (self),
GST_SECOND * 60 * 60 * 1000);
GST_PAD_SET_ACCEPT_INTERSECT (GST_VIDEO_ENCODER_SINK_PAD (self));
}
static void
gst_nv_encoder_finalize (GObject * object)
{
GstNvEncoder *self = GST_NV_ENCODER (object);
delete self->priv;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_nv_encoder_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstNvEncoder *self = GST_NV_ENCODER (object);
GstNvEncoderPrivate *priv = self->priv;
switch (prop_id) {
case PROP_CC_INSERT:
priv->cc_insert = (GstNvEncoderSeiInsertMode) g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_nv_encoder_get_property (GObject * object, guint prop_id, GValue * value,
GParamSpec * pspec)
{
GstNvEncoder *self = GST_NV_ENCODER (object);
GstNvEncoderPrivate *priv = self->priv;
switch (prop_id) {
case PROP_CC_INSERT:
g_value_set_enum (value, priv->cc_insert);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_nv_encoder_set_context (GstElement * element, GstContext * context)
{
GstNvEncoder *self = GST_NV_ENCODER (element);
GstNvEncoderPrivate *priv = self->priv;
std::unique_lock < std::recursive_mutex > lk (priv->context_lock);
switch (priv->selected_device_mode) {
#ifdef G_OS_WIN32
case GST_NV_ENCODER_DEVICE_D3D11:
gst_d3d11_handle_set_context_for_adapter_luid (element,
context, priv->dxgi_adapter_luid, &priv->device);
break;
#endif
case GST_NV_ENCODER_DEVICE_CUDA:
gst_cuda_handle_set_context (element, context, priv->cuda_device_id,
&priv->context);
#ifdef HAVE_CUDA_GST_GL
if (gst_gl_handle_set_context (element, context, &priv->gl_display,
&priv->other_gl_context)) {
if (priv->gl_display)
gst_gl_display_filter_gl_api (priv->gl_display, SUPPORTED_GL_APIS);
}
#endif
break;
default:
break;
}
lk.unlock ();
GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
}
static gboolean
gst_nv_encoder_reset (GstNvEncoder * self)
{
GstNvEncoderPrivate *priv = self->priv;
GST_LOG_OBJECT (self, "Reset");
if (priv->internal_pool) {
gst_buffer_pool_set_active (priv->internal_pool, FALSE);
gst_clear_object (&priv->internal_pool);
}
if (priv->encoding_thread) {
priv->encoding_thread->join ();
priv->encoding_thread = nullptr;
}
priv->object = nullptr;
priv->last_flow = GST_FLOW_OK;
return TRUE;
}
static gboolean
gst_nv_encoder_device_lock (GstNvEncoder * self)
{
GstNvEncoderPrivate *priv = self->priv;
gboolean ret = TRUE;
switch (priv->selected_device_mode) {
#ifdef G_OS_WIN32
case GST_NV_ENCODER_DEVICE_D3D11:
gst_d3d11_device_lock (priv->device);
break;
#endif
case GST_NV_ENCODER_DEVICE_CUDA:
ret = gst_cuda_context_push (priv->context);
break;
default:
break;
}
return ret;
}
static gboolean
gst_nv_encoder_device_unlock (GstNvEncoder * self)
{
GstNvEncoderPrivate *priv = self->priv;
gboolean ret = TRUE;
switch (priv->selected_device_mode) {
#ifdef G_OS_WIN32
case GST_NV_ENCODER_DEVICE_D3D11:
gst_d3d11_device_unlock (priv->device);
break;
#endif
case GST_NV_ENCODER_DEVICE_CUDA:
ret = gst_cuda_context_pop (nullptr);
break;
default:
break;
}
return ret;
}
static gboolean
gst_nv_encoder_drain (GstNvEncoder * self, gboolean locked)
{
GstNvEncoderPrivate *priv = self->priv;
NVENCSTATUS status;
GstNvEncTask *task = nullptr;
if (!priv->object || !priv->encoding_thread)
return TRUE;
GST_DEBUG_OBJECT (self, "Drain");
if (locked)
GST_VIDEO_ENCODER_STREAM_UNLOCK (self);
priv->object->AcquireTask (&task, true);
status = priv->object->Drain (task);
gst_nv_enc_result (status, self);
priv->encoding_thread->join ();
priv->encoding_thread = nullptr;
gst_nv_encoder_reset (self);
if (locked)
GST_VIDEO_ENCODER_STREAM_LOCK (self);
return TRUE;
}
#ifdef G_OS_WIN32
static gboolean
gst_nv_encoder_open_d3d11_device (GstNvEncoder * self)
{
GstNvEncoderPrivate *priv = self->priv;
ComPtr < ID3D10Multithread > multi_thread;
ID3D11Device *device_handle;
HRESULT hr;
if (!gst_d3d11_ensure_element_data_for_adapter_luid (GST_ELEMENT (self),
priv->dxgi_adapter_luid, &priv->device)) {
GST_ERROR_OBJECT (self, "Cannot create d3d11device");
return FALSE;
}
device_handle = gst_d3d11_device_get_device_handle (priv->device);
hr = device_handle->QueryInterface (IID_PPV_ARGS (&multi_thread));
if (!gst_d3d11_result (hr, priv->device)) {
GST_ERROR_OBJECT (self, "ID3D10Multithread interface is unavailable");
gst_clear_object (&priv->device);
return FALSE;
}
multi_thread->SetMultithreadProtected (TRUE);
return TRUE;
}
#endif
static gboolean
gst_nv_encoder_open (GstVideoEncoder * encoder)
{
GstNvEncoder *self = GST_NV_ENCODER (encoder);
GstNvEncoderPrivate *priv = self->priv;
switch (priv->selected_device_mode) {
case GST_NV_ENCODER_DEVICE_AUTO_SELECT:
/* Will open GPU later */
return TRUE;
#ifdef G_OS_WIN32
case GST_NV_ENCODER_DEVICE_D3D11:
return gst_nv_encoder_open_d3d11_device (self);
#endif
case GST_NV_ENCODER_DEVICE_CUDA:
if (!gst_cuda_ensure_element_context (GST_ELEMENT_CAST (encoder),
priv->cuda_device_id, &priv->context)) {
GST_ERROR_OBJECT (self, "failed to create CUDA context");
return FALSE;
}
if (!priv->stream && gst_nvenc_have_set_io_cuda_streams ())
priv->stream = gst_cuda_stream_new (priv->context);
break;
default:
g_assert_not_reached ();
return FALSE;
}
return TRUE;
}
static gboolean
gst_nv_encoder_close (GstVideoEncoder * encoder)
{
GstNvEncoder *self = GST_NV_ENCODER (encoder);
GstNvEncoderPrivate *priv = self->priv;
gst_clear_cuda_stream (&priv->stream);
gst_clear_object (&priv->context);
#ifdef G_OS_WIN32
gst_clear_d3d11_fence (&priv->fence);
gst_clear_object (&priv->device);
#endif
#ifdef HAVE_CUDA_GST_GL
gst_clear_object (&priv->gl_display);
gst_clear_object (&priv->gl_context);
gst_clear_object (&priv->other_gl_context);
#endif
return TRUE;
}
static gboolean
gst_nv_encoder_stop (GstVideoEncoder * encoder)
{
GstNvEncoder *self = GST_NV_ENCODER (encoder);
GstNvEncoderPrivate *priv = self->priv;
GST_DEBUG_OBJECT (self, "Stop");
gst_nv_encoder_drain (self, FALSE);
if (priv->subclass_device_mode == GST_NV_ENCODER_DEVICE_AUTO_SELECT) {
gst_clear_cuda_stream (&priv->stream);
gst_clear_object (&priv->context);
#ifdef G_OS_WIN32
gst_clear_object (&priv->device);
#endif
priv->selected_device_mode = GST_NV_ENCODER_DEVICE_AUTO_SELECT;
}
g_clear_pointer (&priv->input_state, gst_video_codec_state_unref);
return TRUE;
}
static gboolean
gst_nv_encoder_sink_event (GstVideoEncoder * encoder, GstEvent * event)
{
GstNvEncoder *self = GST_NV_ENCODER (encoder);
GstNvEncoderPrivate *priv = self->priv;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_FLUSH_START:
if (priv->object)
priv->object->SetFlushing (TRUE);
break;
default:
break;
}
return GST_VIDEO_ENCODER_CLASS (parent_class)->sink_event (encoder, event);
}
#ifdef HAVE_CUDA_GST_GL
static void
gst_nv_encoder_check_cuda_device_from_gl_context (GstGLContext * context,
gboolean * ret)
{
guint device_count = 0;
CUdevice device_list[1] = { 0, };
CUresult cuda_ret;
*ret = FALSE;
cuda_ret = CuGLGetDevices (&device_count,
device_list, 1, CU_GL_DEVICE_LIST_ALL);
if (!gst_cuda_result (cuda_ret) || device_count == 0)
return;
*ret = TRUE;
}
static gboolean
gst_nv_encoder_ensure_gl_context (GstNvEncoder * self)
{
GstNvEncoderPrivate *priv = self->priv;
gboolean ret = FALSE;
std::unique_lock < std::recursive_mutex > lk (priv->context_lock);
if (!gst_gl_ensure_element_data (GST_ELEMENT (self), &priv->gl_display,
&priv->other_gl_context)) {
GST_DEBUG_OBJECT (self, "Couldn't get GL display");
return FALSE;
}
gst_gl_display_filter_gl_api (priv->gl_display, SUPPORTED_GL_APIS);
if (!gst_gl_display_ensure_context (priv->gl_display, priv->other_gl_context,
&priv->gl_context, nullptr)) {
GST_DEBUG_OBJECT (self, "Couldn't get GL context");
return FALSE;
}
gst_gl_context_thread_add (priv->gl_context,
(GstGLContextThreadFunc) gst_nv_encoder_check_cuda_device_from_gl_context,
&ret);
return ret;
}
#endif
static gboolean
gst_nv_encoder_handle_context_query (GstNvEncoder * self, GstQuery * query)
{
GstNvEncoderPrivate *priv = self->priv;
gboolean ret = FALSE;
std::unique_lock < std::recursive_mutex > lk (priv->context_lock);
switch (priv->selected_device_mode) {
#ifdef G_OS_WIN32
case GST_NV_ENCODER_DEVICE_D3D11:
ret = gst_d3d11_handle_context_query (GST_ELEMENT (self),
query, priv->device);
break;
#endif
case GST_NV_ENCODER_DEVICE_CUDA:
#ifdef HAVE_CUDA_GST_GL
{
GstGLDisplay *display = nullptr;
GstGLContext *other = nullptr;
GstGLContext *local = nullptr;
if (priv->gl_display)
display = (GstGLDisplay *) gst_object_ref (priv->gl_display);
if (priv->gl_context)
local = (GstGLContext *) gst_object_ref (priv->gl_context);
if (priv->other_gl_context)
other = (GstGLContext *) gst_object_ref (priv->other_gl_context);
lk.unlock ();
ret = gst_gl_handle_context_query (GST_ELEMENT (self), query,
display, local, other);
lk.lock ();
gst_clear_object (&display);
gst_clear_object (&other);
gst_clear_object (&local);
if (ret)
return ret;
}
#endif
ret = gst_cuda_handle_context_query (GST_ELEMENT (self),
query, priv->context);
break;
default:
break;
}
return ret;
}
static gboolean
gst_nv_encoder_sink_query (GstVideoEncoder * encoder, GstQuery * query)
{
GstNvEncoder *self = GST_NV_ENCODER (encoder);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONTEXT:
if (gst_nv_encoder_handle_context_query (self, query))
return TRUE;
break;
default:
break;
}
return GST_VIDEO_ENCODER_CLASS (parent_class)->sink_query (encoder, query);
}
static gboolean
gst_nv_encoder_src_query (GstVideoEncoder * encoder, GstQuery * query)
{
GstNvEncoder *self = GST_NV_ENCODER (encoder);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONTEXT:
if (gst_nv_encoder_handle_context_query (self, query))
return TRUE;
break;
default:
break;
}
return GST_VIDEO_ENCODER_CLASS (parent_class)->src_query (encoder, query);
}
static guint
gst_nv_encoder_get_task_size (GstNvEncoder * self)
{
std::shared_ptr < GstNvEncObject > object = self->priv->object;
if (!object)
return 0;
return object->GetTaskSize ();
}
static gboolean
gst_nv_encoder_propose_allocation (GstVideoEncoder * encoder, GstQuery * query)
{
GstNvEncoder *self = GST_NV_ENCODER (encoder);
GstNvEncoderPrivate *priv = self->priv;
GstVideoInfo info;
GstBufferPool *pool = NULL;
GstCaps *caps;
guint size;
GstStructure *config;
GstCapsFeatures *features;
guint min_buffers;
gboolean use_cuda_pool = FALSE;
gst_query_parse_allocation (query, &caps, NULL);
if (!caps) {
GST_WARNING_OBJECT (self, "null caps in query");
return FALSE;
}
if (!gst_video_info_from_caps (&info, caps)) {
GST_WARNING_OBJECT (self, "Failed to convert caps into info");
return FALSE;
}
features = gst_caps_get_features (caps, 0);
min_buffers = gst_nv_encoder_get_task_size (self);
if (min_buffers == 0) {
GstNvEncoderClass *klass = GST_NV_ENCODER_GET_CLASS (self);
min_buffers = klass->calculate_min_buffers (self);
}
switch (priv->subclass_device_mode) {
case GST_NV_ENCODER_DEVICE_AUTO_SELECT:
/* Use upstream pool in case of auto select mode. We don't know which
* GPU to use at this moment */
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, nullptr);
gst_query_add_allocation_pool (query, nullptr, info.size, min_buffers, 0);
return TRUE;
#ifdef G_OS_WIN32
case GST_NV_ENCODER_DEVICE_D3D11:
if (features && gst_caps_features_contains (features,
GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY)) {
GST_DEBUG_OBJECT (self, "upstream support d3d11 memory");
pool = gst_d3d11_buffer_pool_new (priv->device);
}
break;
#endif
case GST_NV_ENCODER_DEVICE_CUDA:
#ifdef HAVE_CUDA_GST_GL
if (features && gst_caps_features_contains (features,
GST_CAPS_FEATURE_MEMORY_GL_MEMORY)) {
GST_DEBUG_OBJECT (self, "upstream support GL memory");
if (!gst_nv_encoder_ensure_gl_context (self)) {
GST_WARNING_OBJECT (self, "Couldn't get GL context");
priv->gl_interop = FALSE;
gst_query_add_allocation_meta (query,
GST_VIDEO_META_API_TYPE, nullptr);
gst_query_add_allocation_pool (query,
nullptr, info.size, min_buffers, 0);
return TRUE;
}
pool = gst_gl_buffer_pool_new (priv->gl_context);
break;
}
#endif
if (features && gst_caps_features_contains (features,
GST_CAPS_FEATURE_MEMORY_CUDA_MEMORY)) {
GST_DEBUG_OBJECT (self, "upstream support CUDA memory");
pool = gst_cuda_buffer_pool_new (priv->context);
use_cuda_pool = TRUE;
}
break;
default:
g_assert_not_reached ();
return FALSE;
}
if (!pool)
pool = gst_video_buffer_pool_new ();
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
size = GST_VIDEO_INFO_SIZE (&info);
gst_buffer_pool_config_set_params (config, caps, size, min_buffers, 0);
if (use_cuda_pool && priv->stream) {
/* Set our stream on buffer pool config so that CUstream can be shared */
gst_buffer_pool_config_set_cuda_stream (config, priv->stream);
}
if (!gst_buffer_pool_set_config (pool, config)) {
GST_WARNING_OBJECT (self, "Failed to set pool config");
gst_object_unref (pool);
return FALSE;
}
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_get_params (config, NULL, &size, NULL, NULL);
gst_structure_free (config);
gst_query_add_allocation_pool (query, pool, size, min_buffers, 0);
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
gst_object_unref (pool);
return TRUE;
}
static NV_ENC_PIC_STRUCT
gst_nv_encoder_get_pic_struct (GstNvEncoder * self, GstBuffer * buffer)
{
GstNvEncoderPrivate *priv = self->priv;
GstVideoInfo *info = &priv->input_state->info;
if (!GST_VIDEO_INFO_IS_INTERLACED (info))
return NV_ENC_PIC_STRUCT_FRAME;
if (GST_VIDEO_INFO_INTERLACE_MODE (info) == GST_VIDEO_INTERLACE_MODE_MIXED) {
if (!GST_BUFFER_FLAG_IS_SET (buffer, GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
return NV_ENC_PIC_STRUCT_FRAME;
}
if (GST_BUFFER_FLAG_IS_SET (buffer, GST_VIDEO_BUFFER_FLAG_TFF))
return NV_ENC_PIC_STRUCT_FIELD_TOP_BOTTOM;
return NV_ENC_PIC_STRUCT_FIELD_BOTTOM_TOP;
}
switch (GST_VIDEO_INFO_FIELD_ORDER (info)) {
case GST_VIDEO_FIELD_ORDER_TOP_FIELD_FIRST:
return NV_ENC_PIC_STRUCT_FIELD_TOP_BOTTOM;
break;
case GST_VIDEO_FIELD_ORDER_BOTTOM_FIELD_FIRST:
return NV_ENC_PIC_STRUCT_FIELD_BOTTOM_TOP;
break;
default:
break;
}
if (GST_BUFFER_FLAG_IS_SET (buffer, GST_VIDEO_BUFFER_FLAG_TFF))
return NV_ENC_PIC_STRUCT_FIELD_TOP_BOTTOM;
return NV_ENC_PIC_STRUCT_FIELD_BOTTOM_TOP;
}
static GstVideoCodecFrame *
gst_nv_encoder_find_output_frame (GstVideoEncoder * self, GstNvEncTask * task)
{
GList *frames, *iter;
GstVideoCodecFrame *ret = NULL;
frames = gst_video_encoder_get_frames (self);
for (iter = frames; iter; iter = g_list_next (iter)) {
GstVideoCodecFrame *frame = (GstVideoCodecFrame *) iter->data;
GstNvEncTask *other = (GstNvEncTask *)
gst_video_codec_frame_get_user_data (frame);
if (!other)
continue;
if (other == task) {
ret = frame;
break;
}
}
if (ret)
gst_video_codec_frame_ref (ret);
if (frames)
g_list_free_full (frames, (GDestroyNotify) gst_video_codec_frame_unref);
return ret;
}
static void
gst_nv_encoder_thread_func (GstNvEncoder * self)
{
GstVideoEncoder *encoder = GST_VIDEO_ENCODER (self);
GstNvEncoderClass *klass = GST_NV_ENCODER_GET_CLASS (self);
GstNvEncoderPrivate *priv = self->priv;
std::shared_ptr < GstNvEncObject > object = priv->object;
GST_INFO_OBJECT (self, "Entering encoding loop");
do {
GstFlowReturn ret;
GstNvEncTask *task = nullptr;
GstVideoCodecFrame *frame;
NV_ENC_LOCK_BITSTREAM bitstream;
NVENCSTATUS status;
ret = object->GetOutput (&task);
if (ret == GST_FLOW_EOS) {
g_assert (!task);
GST_INFO_OBJECT (self, "Got EOS task");
break;
}
frame = gst_nv_encoder_find_output_frame (encoder, task);
if (!frame) {
gst_nv_enc_task_unref (task);
GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL),
("Failed to find associated codec frame"));
priv->last_flow = GST_FLOW_ERROR;
continue;
}
status = gst_nv_enc_task_lock_bitstream (task, &bitstream);
if (status != NV_ENC_SUCCESS) {
gst_nv_enc_task_unref (task);
gst_video_encoder_finish_frame (encoder, frame);
GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL),
("Failed to lock bitstream, status: %" GST_NVENC_STATUS_FORMAT,
GST_NVENC_STATUS_ARGS (status)));
priv->last_flow = GST_FLOW_ERROR;
continue;
}
if (priv->last_flow != GST_FLOW_OK) {
gst_nv_enc_task_unlock_bitstream (task);
gst_nv_enc_task_unref (task);
continue;
}
if (klass->create_output_buffer) {
frame->output_buffer = klass->create_output_buffer (self, &bitstream);
} else {
frame->output_buffer =
gst_buffer_new_memdup (bitstream.bitstreamBufferPtr,
bitstream.bitstreamSizeInBytes);
}
GST_BUFFER_FLAG_SET (frame->output_buffer, GST_BUFFER_FLAG_MARKER);
if (bitstream.pictureType == NV_ENC_PIC_TYPE_IDR)
GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
frame->dts = frame->pts - priv->dts_offset;
frame->pts = bitstream.outputTimeStamp;
frame->duration = bitstream.outputDuration;
gst_nv_enc_task_unlock_bitstream (task);
gst_nv_enc_task_unref (task);
priv->last_flow = gst_video_encoder_finish_frame (encoder, frame);
if (priv->last_flow != GST_FLOW_OK) {
GST_INFO_OBJECT (self,
"Finish frame returned %s", gst_flow_get_name (priv->last_flow));
}
} while (true);
GST_INFO_OBJECT (self, "Exiting thread");
}
static guint
gst_nv_encoder_calculate_task_pool_size (GstNvEncoder * self,
NV_ENC_CONFIG * config)
{
guint num_tasks;
/* At least 4 surfaces are required as documented by Nvidia Encoder guide */
num_tasks = 4;
/* lookahead depth */
num_tasks += config->rcParams.lookaheadDepth;
/* B frames + 1 */
num_tasks += MAX (0, config->frameIntervalP - 1) + 1;
GST_DEBUG_OBJECT (self, "Calculated task pool size: %d "
"(lookahead %d, frameIntervalP %d)",
num_tasks, config->rcParams.lookaheadDepth, config->frameIntervalP);
return num_tasks;
}
static gboolean
gst_nv_encoder_open_encode_session (GstNvEncoder * self)
{
GstNvEncoderPrivate *priv = self->priv;
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = { 0, };
session_params.version =
gst_nvenc_get_open_encode_session_ex_params_version ();
session_params.apiVersion = gst_nvenc_get_api_version ();
GstObject *device = (GstObject *) priv->context;
switch (priv->selected_device_mode) {
#ifdef G_OS_WIN32
case GST_NV_ENCODER_DEVICE_D3D11:
session_params.deviceType = NV_ENC_DEVICE_TYPE_DIRECTX;
session_params.device = gst_d3d11_device_get_device_handle (priv->device);
device = (GstObject *) priv->device;
break;
#endif
case GST_NV_ENCODER_DEVICE_CUDA:
session_params.deviceType = NV_ENC_DEVICE_TYPE_CUDA;
session_params.device = gst_cuda_context_get_handle (priv->context);
break;
default:
g_assert_not_reached ();
return FALSE;
}
priv->object = GstNvEncObject::CreateInstance (GST_ELEMENT_CAST (self),
device, &session_params);
if (!priv->object) {
GST_ERROR_OBJECT (self, "Couldn't create encoder session");
return FALSE;
}
return TRUE;
}
#ifdef G_OS_WIN32
static GstBufferPool *
gst_nv_encoder_create_d3d11_pool (GstNvEncoder * self,
GstVideoCodecState * state)
{
GstNvEncoderPrivate *priv = self->priv;
GstStructure *config;
GstBufferPool *pool = NULL;
GstD3D11AllocationParams *params;
params = gst_d3d11_allocation_params_new (priv->device, &state->info,
GST_D3D11_ALLOCATION_FLAG_DEFAULT, 0, D3D11_RESOURCE_MISC_SHARED);
pool = gst_d3d11_buffer_pool_new (priv->device);
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_set_d3d11_allocation_params (config, params);
gst_d3d11_allocation_params_free (params);
gst_buffer_pool_config_set_params (config, state->caps,
GST_VIDEO_INFO_SIZE (&state->info), 0, 0);
if (!gst_buffer_pool_set_config (pool, config)) {
GST_ERROR_OBJECT (self, "Failed to set pool config");
gst_object_unref (pool);
return NULL;
}
if (!gst_buffer_pool_set_active (pool, TRUE)) {
GST_ERROR_OBJECT (self, "Failed to set active");
gst_object_unref (pool);
return NULL;
}
return pool;
}
#endif
static GstBufferPool *
gst_nv_encoder_create_pool (GstNvEncoder * self, GstVideoCodecState * state)
{
GstNvEncoderPrivate *priv = self->priv;
GstStructure *config;
GstBufferPool *pool = NULL;
/* At this moment device type must be selected already */
switch (priv->selected_device_mode) {
#ifdef G_OS_WIN32
case GST_NV_ENCODER_DEVICE_D3D11:
return gst_nv_encoder_create_d3d11_pool (self, state);
#endif
case GST_NV_ENCODER_DEVICE_CUDA:
pool = gst_cuda_buffer_pool_new (priv->context);
break;
default:
g_assert_not_reached ();
return FALSE;
}
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_set_params (config, state->caps,
GST_VIDEO_INFO_SIZE (&state->info), 0, 0);
if (priv->selected_device_mode == GST_NV_ENCODER_DEVICE_CUDA && priv->stream)
gst_buffer_pool_config_set_cuda_stream (config, priv->stream);
if (!gst_buffer_pool_set_config (pool, config)) {
GST_ERROR_OBJECT (self, "Failed to set pool config");
gst_object_unref (pool);
return NULL;
}
if (!gst_buffer_pool_set_active (pool, TRUE)) {
GST_ERROR_OBJECT (self, "Failed to set active");
gst_object_unref (pool);
return NULL;
}
return pool;
}
static gboolean
gst_nv_encoder_init_session (GstNvEncoder * self, GstBuffer * in_buf)
{
GstNvEncoderPrivate *priv = self->priv;
GstNvEncoderClass *klass = GST_NV_ENCODER_GET_CLASS (self);
GstVideoCodecState *state = priv->input_state;
GstVideoInfo *info = &state->info;
NVENCSTATUS status;
guint task_pool_size;
gint fps_n, fps_d;
GstClockTime frame_duration, min_latency, max_latency;
gst_nv_encoder_reset (self);
memset (&priv->init_params, 0, sizeof (NV_ENC_INITIALIZE_PARAMS));
memset (&priv->config, 0, sizeof (NV_ENC_CONFIG));
if (priv->selected_device_mode == GST_NV_ENCODER_DEVICE_AUTO_SELECT) {
GstNvEncoderDeviceData data;
gboolean ret;
if (!in_buf) {
GST_DEBUG_OBJECT (self, "Unknown device mode, open session later");
return TRUE;
}
if (!klass->select_device (self, info, in_buf, &data)) {
GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL),
("Failed to select device mode"));
return FALSE;
}
GST_DEBUG_OBJECT (self,
"Selected device mode: %d, cuda-device-id: %d, adapter-luid %"
G_GINT64_FORMAT, data.device_mode, data.cuda_device_id,
data.adapter_luid);
g_assert (data.device_mode == GST_NV_ENCODER_DEVICE_CUDA ||
data.device_mode == GST_NV_ENCODER_DEVICE_D3D11);
std::lock_guard < std::recursive_mutex > clk (priv->context_lock);
priv->selected_device_mode = data.device_mode;
priv->cuda_device_id = data.cuda_device_id;
priv->dxgi_adapter_luid = data.adapter_luid;
gst_clear_object (&priv->context);
if (data.device_mode == GST_NV_ENCODER_DEVICE_CUDA) {
GstMemory *mem = gst_buffer_peek_memory (in_buf, 0);
priv->context = (GstCudaContext *) data.device;
gst_clear_cuda_stream (&priv->stream);
if (gst_nvenc_have_set_io_cuda_streams ()) {
if (gst_is_cuda_memory (mem)) {
/* Use upstream CUDA stream */
priv->stream =
gst_cuda_memory_get_stream (GST_CUDA_MEMORY_CAST (mem));
if (priv->stream)
gst_cuda_stream_ref (priv->stream);
}
}
}
#ifdef G_OS_WIN32
gst_clear_object (&priv->device);
if (data.device_mode == GST_NV_ENCODER_DEVICE_D3D11)
priv->device = (GstD3D11Device *) data.device;
#endif
ret = gst_nv_encoder_open (GST_VIDEO_ENCODER (self));
if (!ret) {
GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL),
("Failed to open device"));
return FALSE;
}
}
priv->internal_pool = gst_nv_encoder_create_pool (self, state);
if (!priv->internal_pool) {
GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL),
("Failed to create internal pool"));
return FALSE;
}
if (!gst_nv_encoder_device_lock (self)) {
GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL), ("Failed to lock device"));
gst_nv_encoder_reset (self);
return FALSE;
}
if (!gst_nv_encoder_open_encode_session (self)) {
GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL),
("Failed to open session"));
goto error;
}
if (!klass->set_format (self, state, priv->object->GetHandle (),
&priv->init_params, &priv->config)) {
GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL), ("Failed to set format"));
goto error;
}
task_pool_size = gst_nv_encoder_calculate_task_pool_size (self,
&priv->config);
priv->init_params.encodeConfig = &priv->config;
status = priv->object->InitSession (&priv->init_params,
priv->stream, &priv->input_state->info, task_pool_size);
if (!gst_nv_enc_result (status, self)) {
GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL),
("Failed to init encoder, status: %"
GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)));
goto error;
}
gst_nv_encoder_device_unlock (self);
if (!klass->set_output_state (self, priv->input_state,
priv->object->GetHandle ())) {
GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL),
("Failed to set output state"));
gst_nv_encoder_reset (self);
return FALSE;
}
priv->encoding_thread = std::make_unique < std::thread >
(gst_nv_encoder_thread_func, self);
if (info->fps_n > 0 && info->fps_d > 0) {
fps_n = info->fps_n;
fps_d = info->fps_d;
} else {
fps_n = 25;
fps_d = 1;
}
frame_duration = gst_util_uint64_scale (GST_SECOND, fps_d, fps_n);
priv->dts_offset = 0;
/* Calculate DTS offset for B frame. NVENC does not provide DTS */
if (priv->config.frameIntervalP > 1)
priv->dts_offset = frame_duration * (priv->config.frameIntervalP - 1);
min_latency = priv->dts_offset +
priv->config.rcParams.lookaheadDepth * frame_duration;
max_latency = frame_duration * task_pool_size;
gst_video_encoder_set_latency (GST_VIDEO_ENCODER (self),
min_latency, max_latency);
return TRUE;
error:
gst_nv_encoder_device_unlock (self);
gst_nv_encoder_reset (self);
return FALSE;
}
static gboolean
gst_nv_encoder_reconfigure_session (GstNvEncoder * self)
{
GstNvEncoderPrivate *priv = self->priv;
NV_ENC_RECONFIGURE_PARAMS params = { 0, };
NVENCSTATUS status;
if (!priv->object) {
GST_WARNING_OBJECT (self,
"Encoding session was not configured, open session");
gst_nv_encoder_drain (self, TRUE);
return gst_nv_encoder_init_session (self, nullptr);
}
params.version = gst_nvenc_get_reconfigure_params_version ();
params.reInitEncodeParams = priv->init_params;
params.reInitEncodeParams.encodeConfig = &priv->config;
status = priv->object->Reconfigure (&params);
if (!gst_nv_enc_result (status, self)) {
gst_nv_encoder_drain (self, TRUE);
return gst_nv_encoder_init_session (self, nullptr);
}
return TRUE;
}
static gboolean
gst_nv_encoder_set_format (GstVideoEncoder * encoder,
GstVideoCodecState * state)
{
GstNvEncoder *self = GST_NV_ENCODER (encoder);
GstNvEncoderPrivate *priv = self->priv;
gst_nv_encoder_drain (self, TRUE);
g_clear_pointer (&priv->input_state, gst_video_codec_state_unref);
priv->input_state = gst_video_codec_state_ref (state);
priv->last_flow = GST_FLOW_OK;
#ifdef HAVE_CUDA_GST_GL
{
GstCapsFeatures *features = gst_caps_get_features (state->caps, 0);
if (gst_caps_features_contains (features,
GST_CAPS_FEATURE_MEMORY_GL_MEMORY)) {
priv->gl_interop = TRUE;
} else {
priv->gl_interop = FALSE;
}
}
#endif
/* select device again on next buffer */
if (priv->subclass_device_mode == GST_NV_ENCODER_DEVICE_AUTO_SELECT)
priv->selected_device_mode = GST_NV_ENCODER_DEVICE_AUTO_SELECT;
return gst_nv_encoder_init_session (self, nullptr);
}
static GstFlowReturn
gst_nv_encoder_copy_system (GstNvEncoder * self, const GstVideoInfo * info,
GstBuffer * buffer, GstNvEncTask * task)
{
std::shared_ptr < GstNvEncObject > object = self->priv->object;
NVENCSTATUS status;
GstVideoFrame frame;
guint8 *dst_data;
guint32 pitch;
GstNvEncBuffer *inbuf = nullptr;
if (!gst_video_frame_map (&frame, info, buffer, GST_MAP_READ)) {
GST_ERROR_OBJECT (self, "Failed to map buffer");
return GST_FLOW_ERROR;
}
status = object->AcquireBuffer (&inbuf);
if (!gst_nv_enc_result (status, self)) {
gst_video_frame_unmap (&frame);
return GST_FLOW_ERROR;
}
status = gst_nv_enc_buffer_lock (inbuf, (gpointer *) & dst_data, &pitch);
if (!gst_nv_enc_result (status, self)) {
gst_video_frame_unmap (&frame);
gst_nv_enc_buffer_unref (inbuf);
return GST_FLOW_ERROR;
}
for (guint i = 0; i < GST_VIDEO_FRAME_N_PLANES (&frame); i++) {
guint8 *src_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&frame, i);
guint width_in_bytes = GST_VIDEO_FRAME_COMP_WIDTH (&frame, i) *
GST_VIDEO_FRAME_COMP_PSTRIDE (&frame, i);
guint stride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame, i);
guint height = GST_VIDEO_FRAME_COMP_HEIGHT (&frame, i);
for (guint j = 0; j < height; j++) {
memcpy (dst_data, src_data, width_in_bytes);
dst_data += pitch;
src_data += stride;
}
}
gst_nv_enc_buffer_unlock (inbuf);
gst_video_frame_unmap (&frame);
gst_nv_enc_task_set_buffer (task, inbuf);
return GST_FLOW_OK;
}
#ifdef HAVE_CUDA_GST_GL
static GstCudaGraphicsResource *
gst_nv_encoder_ensure_gl_cuda_resource (GstNvEncoder * self, GstMemory * mem)
{
GQuark quark;
GstNvEncoderPrivate *priv = self->priv;
GstCudaGraphicsResource *resource;
if (!gst_is_gl_memory_pbo (mem)) {
GST_WARNING_OBJECT (self, "memory is not GL PBO memory, %s",
mem->allocator->mem_type);
return nullptr;
}
quark = gst_cuda_quark_from_id (GST_CUDA_QUARK_GRAPHICS_RESOURCE);
resource = (GstCudaGraphicsResource *)
gst_mini_object_get_qdata (GST_MINI_OBJECT (mem), quark);
if (!resource) {
GstMapInfo map_info;
GstGLMemoryPBO *pbo = (GstGLMemoryPBO *) mem;
GstGLBuffer *gl_buf = pbo->pbo;
gboolean ret;
if (!gst_memory_map (mem, &map_info,
(GstMapFlags) (GST_MAP_READ | GST_MAP_GL))) {
GST_ERROR_OBJECT (self, "Couldn't map gl memory");
return nullptr;
}
resource = gst_cuda_graphics_resource_new (priv->context,
GST_OBJECT (GST_GL_BASE_MEMORY_CAST (mem)->context),
GST_CUDA_GRAPHICS_RESOURCE_GL_BUFFER);
GST_LOG_OBJECT (self, "registering gl buffer %d to CUDA", gl_buf->id);
ret = gst_cuda_graphics_resource_register_gl_buffer (resource, gl_buf->id,
CU_GRAPHICS_REGISTER_FLAGS_NONE);
gst_memory_unmap (mem, &map_info);
if (!ret) {
GST_ERROR_OBJECT (self, "Couldn't register gl buffer %d", gl_buf->id);
gst_cuda_graphics_resource_free (resource);
return nullptr;
}
gst_mini_object_set_qdata (GST_MINI_OBJECT (mem), quark, resource,
(GDestroyNotify) gst_cuda_graphics_resource_free);
}
return resource;
}
struct GstNvEncGLInteropData
{
GstNvEncoder *self = nullptr;
GstBuffer *in_buf = nullptr;
GstBuffer *out_buf = nullptr;
};
static void
gst_nv_encoder_upload_gl (GstGLContext * context, GstNvEncGLInteropData * data)
{
GstNvEncoder *self = data->self;
GstNvEncoderPrivate *priv = self->priv;
CUDA_MEMCPY2D copy_param;
GstCudaGraphicsResource *gst_res[GST_VIDEO_MAX_PLANES] = { nullptr, };
CUgraphicsResource cuda_res[GST_VIDEO_MAX_PLANES] = { nullptr, };
CUdeviceptr src_devptr[GST_VIDEO_MAX_PLANES] = { 0, };
const GstVideoInfo *info = &priv->input_state->info;
CUstream stream = gst_cuda_stream_get_handle (priv->stream);
GstCudaMemory *cmem;
GstMapInfo map_info;
CUresult cuda_ret;
gboolean ret = FALSE;
gst_cuda_context_push (priv->context);
for (guint i = 0; i < GST_VIDEO_INFO_N_PLANES (info); i++) {
GstMemory *mem = gst_buffer_peek_memory (data->in_buf, i);
GstGLMemoryPBO *pbo = (GstGLMemoryPBO *) mem;
gsize src_size;
if (!gst_is_gl_memory_pbo (mem)) {
GST_ERROR_OBJECT (self, "Not a GL PBO memory");
goto out;
}
gst_res[i] = gst_nv_encoder_ensure_gl_cuda_resource (self, mem);
if (!gst_res[i]) {
GST_ERROR_OBJECT (self, "Couldn't get resource %d", i);
goto out;
}
gst_gl_memory_pbo_upload_transfer (pbo);
gst_gl_memory_pbo_download_transfer (pbo);
cuda_res[i] = gst_cuda_graphics_resource_map (gst_res[i], stream,
CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY);
if (!cuda_res[i]) {
GST_ERROR_OBJECT (self, "Couldn't map resource");
goto out;
}
cuda_ret = CuGraphicsResourceGetMappedPointer (&src_devptr[i],
&src_size, cuda_res[i]);
if (!gst_cuda_result (cuda_ret)) {
GST_ERROR_OBJECT (self, "Couldn't get mapped device pointer");
goto out;
}
}
if (gst_buffer_pool_acquire_buffer (priv->internal_pool,
&data->out_buf, nullptr) != GST_FLOW_OK) {
GST_ERROR_OBJECT (self, "Couldn't acquire fallback buffer");
goto out;
}
cmem = (GstCudaMemory *) gst_buffer_peek_memory (data->out_buf, 0);
if (!gst_memory_map (GST_MEMORY_CAST (cmem), &map_info,
(GstMapFlags) (GST_MAP_WRITE | GST_MAP_CUDA))) {
GST_ERROR_OBJECT (self, "Couldn't map fallback memory");
goto out;
}
memset (&copy_param, 0, sizeof (CUDA_MEMCPY2D));
for (guint i = 0; i < GST_VIDEO_INFO_N_PLANES (info); i++) {
copy_param.srcMemoryType = CU_MEMORYTYPE_DEVICE;
copy_param.srcDevice = src_devptr[i];
copy_param.srcPitch = GST_VIDEO_INFO_PLANE_STRIDE (info, i);
copy_param.dstMemoryType = CU_MEMORYTYPE_DEVICE;
copy_param.dstDevice = ((CUdeviceptr) map_info.data) + cmem->info.offset[i];
copy_param.dstPitch = cmem->info.stride[0];
copy_param.WidthInBytes = GST_VIDEO_INFO_COMP_WIDTH (info, i) *
GST_VIDEO_INFO_COMP_PSTRIDE (info, i);
copy_param.Height = GST_VIDEO_INFO_COMP_HEIGHT (info, i);
if (!gst_cuda_result (CuMemcpy2DAsync (&copy_param, stream))) {
gst_memory_unmap (GST_MEMORY_CAST (cmem), &map_info);
GST_ERROR_OBJECT (self, "Couldn't copy plane %d", i);
goto out;
}
}
gst_memory_unmap (GST_MEMORY_CAST (cmem), &map_info);
ret = TRUE;
out:
for (guint i = 0; i < gst_buffer_n_memory (data->in_buf); i++) {
if (!gst_res[i])
break;
gst_cuda_graphics_resource_unmap (gst_res[i], stream);
}
CuStreamSynchronize (stream);
gst_cuda_context_pop (nullptr);
if (!ret)
gst_clear_buffer (&data->out_buf);
}
#endif /* HAVE_CUDA_GST_GL */
static GstFlowReturn
gst_nv_encoder_prepare_task_input_cuda (GstNvEncoder * self,
GstBuffer * buffer, GstNvEncTask * task)
{
GstNvEncoderPrivate *priv = self->priv;
std::shared_ptr < GstNvEncObject > object = priv->object;
GstMemory *mem;
GstCudaMemory *cmem;
NVENCSTATUS status;
GstCudaStream *stream;
GstNvEncResource *resource = nullptr;
const GstVideoInfo *info = &priv->input_state->info;
mem = gst_buffer_peek_memory (buffer, 0);
#ifdef HAVE_CUDA_GST_GL
if (priv->gl_interop) {
if (gst_is_gl_memory (mem) && gst_buffer_n_memory (buffer) ==
GST_VIDEO_INFO_N_PLANES (info)) {
GstNvEncGLInteropData gl_data;
GstGLMemory *gl_mem = (GstGLMemory *) mem;
gl_data.self = self;
gl_data.in_buf = buffer;
gl_data.out_buf = nullptr;
gst_gl_context_thread_add (gl_mem->mem.context,
(GstGLContextThreadFunc) gst_nv_encoder_upload_gl, &gl_data);
if (gl_data.out_buf) {
mem = gst_buffer_peek_memory (gl_data.out_buf, 0);
status = object->AcquireResource (mem, &resource);
if (status != NV_ENC_SUCCESS) {
gst_buffer_unref (gl_data.out_buf);
GST_ERROR_OBJECT (self, "Failed to get resource, status %"
GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status));
return GST_FLOW_ERROR;
}
gst_nv_enc_task_set_resource (task, gl_data.out_buf, resource);
return GST_FLOW_OK;
} else {
GST_WARNING_OBJECT (self, "GL interop failed");
priv->gl_interop = FALSE;
}
}
}
#endif
if (!gst_is_cuda_memory (mem)) {
GST_LOG_OBJECT (self, "Not a CUDA buffer, system copy");
return gst_nv_encoder_copy_system (self, info, buffer, task);
}
cmem = GST_CUDA_MEMORY_CAST (mem);
if (cmem->context != priv->context) {
GST_LOG_OBJECT (self, "Different context, system copy");
return gst_nv_encoder_copy_system (self, info, buffer, task);
}
status = object->AcquireResource (mem, &resource);
if (status != NV_ENC_SUCCESS) {
GST_ERROR_OBJECT (self, "Failed to get resource, status %"
GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status));
return GST_FLOW_ERROR;
}
stream = gst_cuda_memory_get_stream (cmem);
if (stream != priv->stream) {
/* different stream, needs sync */
gst_cuda_memory_sync (cmem);
}
gst_nv_enc_task_set_resource (task, gst_buffer_ref (buffer), resource);
return GST_FLOW_OK;
}
#ifdef G_OS_WIN32
static GstBuffer *
gst_nv_encoder_copy_d3d11 (GstNvEncoder * self,
GstBuffer * src_buffer, GstBufferPool * pool, gboolean shared)
{
GstNvEncoderPrivate *priv = self->priv;
D3D11_TEXTURE2D_DESC src_desc, dst_desc;
D3D11_BOX src_box;
guint subresource_idx;
GstMemory *src_mem, *dst_mem;
GstMapInfo src_info, dst_info;
ID3D11Texture2D *src_tex, *dst_tex;
ID3D11Device *device_handle;
ID3D11DeviceContext *device_context;
GstBuffer *dst_buffer;
GstFlowReturn ret;
ComPtr < IDXGIResource > dxgi_resource;
ComPtr < ID3D11Texture2D > shared_texture;
HANDLE shared_handle;
GstD3D11Device *device;
HRESULT hr;
ret = gst_buffer_pool_acquire_buffer (pool, &dst_buffer, NULL);
if (ret != GST_FLOW_OK) {
GST_ERROR_OBJECT (self, "Failed to acquire buffer");
return NULL;
}
src_mem = gst_buffer_peek_memory (src_buffer, 0);
dst_mem = gst_buffer_peek_memory (dst_buffer, 0);
device = GST_D3D11_MEMORY_CAST (src_mem)->device;
device_handle = gst_d3d11_device_get_device_handle (device);
device_context = gst_d3d11_device_get_device_context_handle (device);
if (!gst_memory_map (src_mem, &src_info,
(GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11))) {
GST_WARNING ("Failed to map src memory");
gst_buffer_unref (dst_buffer);
return NULL;
}
if (!gst_memory_map (dst_mem, &dst_info,
(GstMapFlags) (GST_MAP_WRITE | GST_MAP_D3D11))) {
GST_WARNING ("Failed to map dst memory");
gst_memory_unmap (src_mem, &src_info);
gst_buffer_unref (dst_buffer);
return NULL;
}
src_tex = (ID3D11Texture2D *) src_info.data;
dst_tex = (ID3D11Texture2D *) dst_info.data;
gst_d3d11_memory_get_texture_desc (GST_D3D11_MEMORY_CAST (src_mem),
&src_desc);
gst_d3d11_memory_get_texture_desc (GST_D3D11_MEMORY_CAST (dst_mem),
&dst_desc);
subresource_idx =
gst_d3d11_memory_get_subresource_index (GST_D3D11_MEMORY_CAST (src_mem));
if (shared) {
hr = dst_tex->QueryInterface (IID_PPV_ARGS (&dxgi_resource));
if (!gst_d3d11_result (hr, priv->device)) {
GST_ERROR_OBJECT (self,
"IDXGIResource interface is not available, hr: 0x%x", (guint) hr);
goto error;
}
hr = dxgi_resource->GetSharedHandle (&shared_handle);
if (!gst_d3d11_result (hr, priv->device)) {
GST_ERROR_OBJECT (self, "Failed to get shared handle, hr: 0x%x",
(guint) hr);
goto error;
}
hr = device_handle->OpenSharedResource (shared_handle,
IID_PPV_ARGS (&shared_texture));
if (!gst_d3d11_result (hr, device)) {
GST_ERROR_OBJECT (self, "Failed to get shared texture, hr: 0x%x",
(guint) hr);
goto error;
}
dst_tex = shared_texture.Get ();
}
src_box.left = 0;
src_box.top = 0;
src_box.front = 0;
src_box.back = 1;
src_box.right = MIN (src_desc.Width, dst_desc.Width);
src_box.bottom = MIN (src_desc.Height, dst_desc.Height);
if (shared) {
if (priv->fence && priv->fence->device != device)
gst_clear_d3d11_fence (&priv->fence);
if (!priv->fence)
priv->fence = gst_d3d11_device_create_fence (device);
if (!priv->fence) {
GST_ERROR_OBJECT (self, "Couldn't crete fence");
goto error;
}
gst_d3d11_device_lock (device);
}
device_context->CopySubresourceRegion (dst_tex, 0,
0, 0, 0, src_tex, subresource_idx, &src_box);
if (shared) {
if (!gst_d3d11_fence_signal (priv->fence) ||
!gst_d3d11_fence_wait (priv->fence)) {
GST_ERROR_OBJECT (self, "Couldn't sync GPU operation");
gst_d3d11_device_unlock (device);
gst_clear_d3d11_fence (&priv->fence);
goto error;
}
gst_d3d11_device_unlock (device);
}
gst_memory_unmap (dst_mem, &dst_info);
gst_memory_unmap (src_mem, &src_info);
return dst_buffer;
error:
gst_memory_unmap (dst_mem, &dst_info);
gst_memory_unmap (src_mem, &src_info);
gst_buffer_unref (dst_buffer);
return NULL;
}
static GstBuffer *
gst_nv_encoder_upload_d3d11_frame (GstNvEncoder * self,
const GstVideoInfo * info, GstBuffer * buffer, GstBufferPool * pool)
{
GstD3D11Memory *dmem;
D3D11_TEXTURE2D_DESC desc;
dmem = (GstD3D11Memory *) gst_buffer_peek_memory (buffer, 0);
gst_d3d11_memory_get_texture_desc (dmem, &desc);
if (desc.Usage != D3D11_USAGE_DEFAULT) {
GST_TRACE_OBJECT (self, "Not a default usage texture, d3d11 copy");
return gst_nv_encoder_copy_d3d11 (self, buffer, pool, FALSE);
}
GST_TRACE_OBJECT (self, "Use input buffer without copy");
return gst_buffer_ref (buffer);
}
static GstFlowReturn
gst_nv_encoder_prepare_task_input_d3d11 (GstNvEncoder * self,
GstBuffer * buffer, GstNvEncTask * task)
{
GstNvEncoderPrivate *priv = self->priv;
std::shared_ptr < GstNvEncObject > object = priv->object;
GstMemory *mem;
GstD3D11Memory *dmem;
NVENCSTATUS status;
GstBuffer *upload_buffer = nullptr;
GstNvEncResource *resource = nullptr;
const GstVideoInfo *info = &priv->input_state->info;
GstBufferPool *pool = priv->internal_pool;
if (gst_buffer_n_memory (buffer) > 1) {
GST_LOG_OBJECT (self, "Not a native DXGI format, system copy");
return gst_nv_encoder_copy_system (self, info, buffer, task);
}
mem = gst_buffer_peek_memory (buffer, 0);
if (!gst_is_d3d11_memory (mem)) {
GST_LOG_OBJECT (self, "Not a D3D11 buffer, system copy");
return gst_nv_encoder_copy_system (self, info, buffer, task);
}
dmem = GST_D3D11_MEMORY_CAST (mem);
if (dmem->device != priv->device) {
gint64 adapter_luid;
g_object_get (dmem->device, "adapter-luid", &adapter_luid, NULL);
if (adapter_luid == priv->dxgi_adapter_luid) {
GST_LOG_OBJECT (self, "Different device but same GPU, copy d3d11");
upload_buffer = gst_nv_encoder_copy_d3d11 (self, buffer, pool, TRUE);
} else {
GST_LOG_OBJECT (self, "Different device, system copy");
return gst_nv_encoder_copy_system (self, info, buffer, task);
}
}
if (!upload_buffer)
upload_buffer =
gst_nv_encoder_upload_d3d11_frame (self, info, buffer, pool);
if (!upload_buffer) {
GST_ERROR_OBJECT (self, "Failed to upload buffer");
return GST_FLOW_ERROR;
}
status = object->AcquireResource (mem, &resource);
if (status != NV_ENC_SUCCESS) {
GST_ERROR_OBJECT (self, "Failed to get resource, status %"
GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status));
gst_buffer_unref (upload_buffer);
return GST_FLOW_ERROR;
}
gst_nv_enc_task_set_resource (task, upload_buffer, resource);
return GST_FLOW_OK;
}
#endif
static GstFlowReturn
gst_nv_encoder_prepare_task_input (GstNvEncoder * self,
GstBuffer * buffer, GstNvEncTask * task)
{
GstNvEncoderPrivate *priv = self->priv;
GstFlowReturn ret = GST_FLOW_ERROR;
switch (priv->selected_device_mode) {
#ifdef G_OS_WIN32
case GST_NV_ENCODER_DEVICE_D3D11:
ret = gst_nv_encoder_prepare_task_input_d3d11 (self, buffer, task);
break;
#endif
case GST_NV_ENCODER_DEVICE_CUDA:
ret = gst_nv_encoder_prepare_task_input_cuda (self, buffer, task);
break;
default:
g_assert_not_reached ();
break;
}
return ret;
}
static gboolean
gst_nv_encoder_foreach_caption_meta (GstBuffer * buffer, GstMeta ** meta,
GArray * payload)
{
GstVideoCaptionMeta *cc_meta;
GstByteWriter br;
guint payload_size;
NV_ENC_SEI_PAYLOAD sei_payload;
if ((*meta)->info->api != GST_VIDEO_CAPTION_META_API_TYPE)
return TRUE;
cc_meta = (GstVideoCaptionMeta *) (*meta);
if (cc_meta->caption_type != GST_VIDEO_CAPTION_TYPE_CEA708_RAW)
return TRUE;
/* 1 byte contry_code + 10 bytes CEA-708 specific data + caption data */
payload_size = 11 + cc_meta->size;
gst_byte_writer_init_with_size (&br, payload_size, FALSE);
/* 8-bits itu_t_t35_country_code */
gst_byte_writer_put_uint8 (&br, 181);
/* 16-bits itu_t_t35_provider_code */
gst_byte_writer_put_uint8 (&br, 0);
gst_byte_writer_put_uint8 (&br, 49);
/* 32-bits ATSC_user_identifier */
gst_byte_writer_put_uint8 (&br, 'G');
gst_byte_writer_put_uint8 (&br, 'A');
gst_byte_writer_put_uint8 (&br, '9');
gst_byte_writer_put_uint8 (&br, '4');
/* 8-bits ATSC1_data_user_data_type_code */
gst_byte_writer_put_uint8 (&br, 3);
/* 8-bits:
* 1 bit process_em_data_flag (0)
* 1 bit process_cc_data_flag (1)
* 1 bit additional_data_flag (0)
* 5-bits cc_count
*/
gst_byte_writer_put_uint8 (&br, ((cc_meta->size / 3) & 0x1f) | 0x40);
/* 8 bits em_data, unused */
gst_byte_writer_put_uint8 (&br, 255);
gst_byte_writer_put_data (&br, cc_meta->data, cc_meta->size);
/* 8 marker bits */
gst_byte_writer_put_uint8 (&br, 255);
sei_payload.payloadSize = gst_byte_writer_get_pos (&br);
sei_payload.payloadType = 4;
sei_payload.payload = gst_byte_writer_reset_and_get_data (&br);
g_array_append_val (payload, sei_payload);
return TRUE;
}
static GstFlowReturn
gst_nv_encoder_handle_frame (GstVideoEncoder * encoder,
GstVideoCodecFrame * frame)
{
GstNvEncoder *self = GST_NV_ENCODER (encoder);
GstNvEncoderPrivate *priv = self->priv;
GstNvEncoderClass *klass = GST_NV_ENCODER_GET_CLASS (self);
GstFlowReturn ret = GST_FLOW_ERROR;
GstNvEncTask *task = nullptr;
GstNvEncoderReconfigure reconfig;
GstBuffer *in_buf = frame->input_buffer;
NVENCSTATUS status;
if (priv->last_flow != GST_FLOW_OK) {
GST_INFO_OBJECT (self, "Last flow was %s",
gst_flow_get_name (priv->last_flow));
gst_video_encoder_finish_frame (encoder, frame);
return priv->last_flow;
}
if (!priv->object && !gst_nv_encoder_init_session (self, in_buf)) {
GST_ERROR_OBJECT (self, "Encoder object was not configured");
gst_video_encoder_finish_frame (encoder, frame);
return GST_FLOW_NOT_NEGOTIATED;
}
reconfig = klass->check_reconfigure (self, &priv->config);
switch (reconfig) {
case GST_NV_ENCODER_RECONFIGURE_BITRATE:
if (!gst_nv_encoder_reconfigure_session (self)) {
gst_video_encoder_finish_frame (encoder, frame);
return GST_FLOW_NOT_NEGOTIATED;
}
break;
case GST_NV_ENCODER_RECONFIGURE_FULL:
{
gst_nv_encoder_drain (self, TRUE);
if (!gst_nv_encoder_init_session (self, nullptr)) {
gst_video_encoder_finish_frame (encoder, frame);
return GST_FLOW_NOT_NEGOTIATED;
}
break;
}
default:
break;
}
/* Release stream lock temporarily for encoding thread to be able to
* push encoded data */
GST_VIDEO_ENCODER_STREAM_UNLOCK (self);
GST_TRACE_OBJECT (self, "Waiting for new task");
ret = priv->object->AcquireTask (&task, false);
GST_VIDEO_ENCODER_STREAM_LOCK (self);
if (priv->last_flow != GST_FLOW_OK) {
GST_INFO_OBJECT (self, "Last flow was %s",
gst_flow_get_name (priv->last_flow));
gst_video_encoder_finish_frame (encoder, frame);
return priv->last_flow;
}
if (ret != GST_FLOW_OK) {
GST_DEBUG_OBJECT (self, "AcquireTask returned %s", gst_flow_get_name (ret));
gst_video_encoder_finish_frame (encoder, frame);
return ret;
}
gst_nv_encoder_device_lock (self);
ret = gst_nv_encoder_prepare_task_input (self, in_buf, task);
gst_nv_encoder_device_unlock (self);
if (ret != GST_FLOW_OK) {
GST_ERROR_OBJECT (self, "Failed to upload frame");
gst_nv_enc_task_unref (task);
gst_video_encoder_finish_frame (encoder, frame);
return ret;
}
if (priv->cc_insert != GST_NV_ENCODER_SEI_DISABLED) {
gst_buffer_foreach_meta (in_buf,
(GstBufferForeachMetaFunc) gst_nv_encoder_foreach_caption_meta,
gst_nv_enc_task_get_sei_payload (task));
}
status = priv->object->Encode (frame,
gst_nv_encoder_get_pic_struct (self, in_buf), task);
if (status != NV_ENC_SUCCESS) {
GST_ERROR_OBJECT (self, "Failed to encode frame");
gst_video_encoder_finish_frame (encoder, frame);
return GST_FLOW_ERROR;
}
gst_video_codec_frame_unref (frame);
return GST_FLOW_OK;
}
static GstFlowReturn
gst_nv_encoder_finish (GstVideoEncoder * encoder)
{
GstNvEncoder *self = GST_NV_ENCODER (encoder);
GST_DEBUG_OBJECT (self, "Finish");
gst_nv_encoder_drain (self, TRUE);
return GST_FLOW_OK;
}
static gboolean
gst_nv_encoder_flush (GstVideoEncoder * encoder)
{
GstNvEncoder *self = GST_NV_ENCODER (encoder);
GstNvEncoderPrivate *priv = self->priv;
GST_DEBUG_OBJECT (self, "Flush");
gst_nv_encoder_drain (self, TRUE);
priv->last_flow = GST_FLOW_OK;
return TRUE;
}
static gboolean
gst_nv_encoder_transform_meta (GstVideoEncoder * encoder,
GstVideoCodecFrame * frame, GstMeta * meta)
{
GstNvEncoder *self = GST_NV_ENCODER (encoder);
GstNvEncoderPrivate *priv = self->priv;
GstVideoCaptionMeta *cc_meta;
/* We need to handle only case CC meta should be dropped */
if (priv->cc_insert != GST_NV_ENCODER_SEI_INSERT_AND_DROP)
goto out;
if (meta->info->api != GST_VIDEO_CAPTION_META_API_TYPE)
goto out;
cc_meta = (GstVideoCaptionMeta *) meta;
if (cc_meta->caption_type != GST_VIDEO_CAPTION_TYPE_CEA708_RAW)
goto out;
/* Don't copy this meta into output buffer */
return FALSE;
out:
return GST_VIDEO_ENCODER_CLASS (parent_class)->transform_meta (encoder,
frame, meta);
}
void
gst_nv_encoder_set_device_mode (GstNvEncoder * encoder,
GstNvEncoderDeviceMode mode, guint cuda_device_id, gint64 adapter_luid)
{
GstNvEncoderPrivate *priv = encoder->priv;
priv->subclass_device_mode = mode;
priv->selected_device_mode = mode;
priv->cuda_device_id = cuda_device_id;
priv->dxgi_adapter_luid = adapter_luid;
}
/**
* GstNvEncoderPreset:
*
* Since: 1.22
*/
GType
gst_nv_encoder_preset_get_type (void)
{
static GType preset_type = 0;
static const GEnumValue presets[] = {
{GST_NV_ENCODER_PRESET_DEFAULT, "Default", "default"},
{GST_NV_ENCODER_PRESET_HP, "High Performance", "hp"},
{GST_NV_ENCODER_PRESET_HQ, "High Quality", "hq"},
{GST_NV_ENCODER_PRESET_LOW_LATENCY_DEFAULT, "Low Latency", "low-latency"},
{GST_NV_ENCODER_PRESET_LOW_LATENCY_HQ, "Low Latency, High Quality",
"low-latency-hq"},
{GST_NV_ENCODER_PRESET_LOW_LATENCY_HP, "Low Latency, High Performance",
"low-latency-hp"},
{GST_NV_ENCODER_PRESET_LOSSLESS_DEFAULT, "Lossless", "lossless"},
{GST_NV_ENCODER_PRESET_LOSSLESS_HP, "Lossless, High Performance",
"lossless-hp"},
{0, NULL, NULL},
};
if (g_once_init_enter (&preset_type)) {
GType type = g_enum_register_static ("GstNvEncoderPreset", presets);
g_once_init_leave (&preset_type, type);
}
return preset_type;
}
void
gst_nv_encoder_preset_to_guid (GstNvEncoderPreset preset, GUID * guid)
{
switch (preset) {
case GST_NV_ENCODER_PRESET_DEFAULT:
*guid = NV_ENC_PRESET_DEFAULT_GUID;
break;
case GST_NV_ENCODER_PRESET_HP:
*guid = NV_ENC_PRESET_HP_GUID;
break;
case GST_NV_ENCODER_PRESET_HQ:
*guid = NV_ENC_PRESET_HQ_GUID;
break;
case GST_NV_ENCODER_PRESET_LOW_LATENCY_DEFAULT:
*guid = NV_ENC_PRESET_LOW_LATENCY_DEFAULT_GUID;
break;
case GST_NV_ENCODER_PRESET_LOW_LATENCY_HQ:
*guid = NV_ENC_PRESET_LOW_LATENCY_HQ_GUID;
break;
case GST_NV_ENCODER_PRESET_LOW_LATENCY_HP:
*guid = NV_ENC_PRESET_LOW_LATENCY_HP_GUID;
break;
case GST_NV_ENCODER_PRESET_LOSSLESS_DEFAULT:
*guid = NV_ENC_PRESET_LOSSLESS_DEFAULT_GUID;
break;
case GST_NV_ENCODER_PRESET_LOSSLESS_HP:
*guid = NV_ENC_PRESET_LOSSLESS_HP_GUID;
break;
default:
break;
}
*guid = NV_ENC_PRESET_DEFAULT_GUID;
}
/**
* GstNvEncoderRCMode:
*
* Since: 1.22
*/
GType
gst_nv_encoder_rc_mode_get_type (void)
{
static GType rc_mode_type = 0;
static const GEnumValue rc_modes[] = {
{GST_NV_ENCODER_RC_MODE_CONSTQP, "Constant Quantization", "cqp"},
{GST_NV_ENCODER_RC_MODE_VBR, "Variable Bit Rate", "vbr"},
{GST_NV_ENCODER_RC_MODE_CBR, "Constant Bit Rate", "cbr"},
{GST_NV_ENCODER_RC_MODE_CBR_LOWDELAY_HQ,
"Low-Delay CBR, High Quality", "cbr-ld-hq"},
{GST_NV_ENCODER_RC_MODE_CBR_HQ, "CBR, High Quality (slower)", "cbr-hq"},
{GST_NV_ENCODER_RC_MODE_VBR_HQ, "VBR, High Quality (slower)", "vbr-hq"},
{0, NULL, NULL},
};
if (g_once_init_enter (&rc_mode_type)) {
GType type = g_enum_register_static ("GstNvEncoderRCMode", rc_modes);
g_once_init_leave (&rc_mode_type, type);
}
return rc_mode_type;
}
NV_ENC_PARAMS_RC_MODE
gst_nv_encoder_rc_mode_to_native (GstNvEncoderRCMode rc_mode)
{
switch (rc_mode) {
case GST_NV_ENCODER_RC_MODE_CONSTQP:
return NV_ENC_PARAMS_RC_CONSTQP;
case GST_NV_ENCODER_RC_MODE_VBR:
return NV_ENC_PARAMS_RC_VBR;
case GST_NV_ENCODER_RC_MODE_CBR:
return NV_ENC_PARAMS_RC_CBR;
case GST_NV_ENCODER_RC_MODE_CBR_LOWDELAY_HQ:
return NV_ENC_PARAMS_RC_CBR_LOWDELAY_HQ;
case GST_NV_ENCODER_RC_MODE_CBR_HQ:
return NV_ENC_PARAMS_RC_CBR_HQ;
case GST_NV_ENCODER_RC_MODE_VBR_HQ:
return NV_ENC_PARAMS_RC_VBR_HQ;
default:
break;
}
return NV_ENC_PARAMS_RC_VBR;
}
/**
* GstNvEncoderSeiInsertMode:
*
* Since: 1.24
*/
GType
gst_nv_encoder_sei_insert_mode_get_type (void)
{
static GType type = 0;
static const GEnumValue insert_modes[] = {
/**
* GstNvEncoderSeiInsertMode::insert:
*
* Since: 1.24
*/
{GST_NV_ENCODER_SEI_INSERT, "Insert SEI", "insert"},
/**
* GstNvEncoderSeiInsertMode::insert-and-drop:
*
* Since: 1.24
*/
{GST_NV_ENCODER_SEI_INSERT_AND_DROP,
"Insert SEI and remove corresponding meta from output buffer",
"insert-and-drop"},
/**
* GstNvEncoderSeiInsertMode::disabled:
*
* Since: 1.24
*/
{GST_NV_ENCODER_SEI_DISABLED, "Disable SEI insertion", "disabled"},
{0, nullptr, nullptr}
};
GST_CUDA_CALL_ONCE_BEGIN {
type = g_enum_register_static ("GstNvEncoderSeiInsertMode", insert_modes);
} GST_CUDA_CALL_ONCE_END;
return type;
}
GstNvEncoderClassData *
gst_nv_encoder_class_data_new (void)
{
GstNvEncoderClassData *data = g_new0 (GstNvEncoderClassData, 1);
data->ref_count = 1;
return data;
}
GstNvEncoderClassData *
gst_nv_encoder_class_data_ref (GstNvEncoderClassData * cdata)
{
g_atomic_int_add (&cdata->ref_count, 1);
return cdata;
}
void
gst_nv_encoder_class_data_unref (GstNvEncoderClassData * cdata)
{
if (g_atomic_int_dec_and_test (&cdata->ref_count)) {
gst_clear_caps (&cdata->sink_caps);
gst_clear_caps (&cdata->src_caps);
if (cdata->formats)
g_list_free_full (cdata->formats, (GDestroyNotify) g_free);
if (cdata->profiles)
g_list_free_full (cdata->profiles, (GDestroyNotify) g_free);
g_free (cdata);
}
}
void
gst_nv_encoder_get_encoder_caps (gpointer session, const GUID * encode_guid,
GstNvEncoderDeviceCaps * device_caps)
{
GstNvEncoderDeviceCaps dev_caps = { 0, };
NV_ENC_CAPS_PARAM caps_param = { 0, };
NVENCSTATUS status;
GUID guid = *encode_guid;
GST_DEBUG_CATEGORY_INIT (gst_nv_encoder_debug, "nvencoder", 0, "nvencoder");
caps_param.version = gst_nvenc_get_caps_param_version ();
#define CHECK_CAPS(to_query,val,default_val) G_STMT_START { \
gint _val; \
caps_param.capsToQuery = to_query; \
status = NvEncGetEncodeCaps (session, guid, &caps_param, \
&_val); \
if (status != NV_ENC_SUCCESS) { \
GST_WARNING ("Unable to query %s, status: %" \
GST_NVENC_STATUS_FORMAT, G_STRINGIFY (to_query), \
GST_NVENC_STATUS_ARGS (status)); \
val = default_val; \
} else { \
GST_DEBUG ("%s: %d", G_STRINGIFY (to_query), _val); \
val = _val; \
} \
} G_STMT_END
CHECK_CAPS (NV_ENC_CAPS_NUM_MAX_BFRAMES, dev_caps.max_bframes, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORTED_RATECONTROL_MODES,
dev_caps.ratecontrol_modes, NV_ENC_PARAMS_RC_VBR);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_FIELD_ENCODING, dev_caps.field_encoding, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_MONOCHROME, dev_caps.monochrome, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_FMO, dev_caps.fmo, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_QPELMV, dev_caps.qpelmv, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_BDIRECT_MODE, dev_caps.bdirect_mode, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_CABAC, dev_caps.cabac, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_ADAPTIVE_TRANSFORM,
dev_caps.adaptive_transform, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_STEREO_MVC, dev_caps.stereo_mvc, 0);
CHECK_CAPS (NV_ENC_CAPS_NUM_MAX_TEMPORAL_LAYERS, dev_caps.temoral_layers, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_HIERARCHICAL_PFRAMES,
dev_caps.hierarchical_pframes, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_HIERARCHICAL_BFRAMES,
dev_caps.hierarchical_bframes, 0);
CHECK_CAPS (NV_ENC_CAPS_LEVEL_MAX, dev_caps.level_max, 0);
CHECK_CAPS (NV_ENC_CAPS_LEVEL_MIN, dev_caps.level_min, 0);
CHECK_CAPS (NV_ENC_CAPS_SEPARATE_COLOUR_PLANE,
dev_caps.separate_colour_plane, 0);
CHECK_CAPS (NV_ENC_CAPS_WIDTH_MAX, dev_caps.width_max, 4096);
CHECK_CAPS (NV_ENC_CAPS_HEIGHT_MAX, dev_caps.height_max, 4096);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_TEMPORAL_SVC, dev_caps.temporal_svc, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_DYN_RES_CHANGE, dev_caps.dyn_res_change, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_DYN_BITRATE_CHANGE,
dev_caps.dyn_bitrate_change, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_DYN_FORCE_CONSTQP,
dev_caps.dyn_force_constqp, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_DYN_RCMODE_CHANGE,
dev_caps.dyn_rcmode_change, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_SUBFRAME_READBACK,
dev_caps.subframe_readback, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_CONSTRAINED_ENCODING,
dev_caps.constrained_encoding, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_INTRA_REFRESH, dev_caps.intra_refresh, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE,
dev_caps.custom_vbv_buf_size, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_DYNAMIC_SLICE_MODE,
dev_caps.dynamic_slice_mode, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_REF_PIC_INVALIDATION,
dev_caps.ref_pic_invalidation, 0);
CHECK_CAPS (NV_ENC_CAPS_PREPROC_SUPPORT, dev_caps.preproc_support, 0);
/* NOTE: Async is Windows only */
#ifdef G_OS_WIN32
CHECK_CAPS (NV_ENC_CAPS_ASYNC_ENCODE_SUPPORT,
dev_caps.async_encoding_support, 0);
#endif
CHECK_CAPS (NV_ENC_CAPS_MB_NUM_MAX, dev_caps.mb_num_max, 0);
CHECK_CAPS (NV_ENC_CAPS_MB_PER_SEC_MAX, dev_caps.mb_per_sec_max, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_YUV444_ENCODE, dev_caps.yuv444_encode, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_LOSSLESS_ENCODE, dev_caps.lossless_encode, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_SAO, dev_caps.sao, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_MEONLY_MODE, dev_caps.meonly_mode, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_LOOKAHEAD, dev_caps.lookahead, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_TEMPORAL_AQ, dev_caps.temporal_aq, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_10BIT_ENCODE,
dev_caps.supports_10bit_encode, 0);
CHECK_CAPS (NV_ENC_CAPS_NUM_MAX_LTR_FRAMES, dev_caps.num_max_ltr_frames, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_WEIGHTED_PREDICTION,
dev_caps.weighted_prediction, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_BFRAME_REF_MODE, dev_caps.bframe_ref_mode, 0);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_EMPHASIS_LEVEL_MAP,
dev_caps.emphasis_level_map, 0);
CHECK_CAPS (NV_ENC_CAPS_WIDTH_MIN, dev_caps.width_min, 16);
CHECK_CAPS (NV_ENC_CAPS_HEIGHT_MIN, dev_caps.height_min, 16);
CHECK_CAPS (NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES,
dev_caps.multiple_ref_frames, 0);
#undef CHECK_CAPS
*device_caps = dev_caps;
}
void
gst_nv_encoder_merge_device_caps (const GstNvEncoderDeviceCaps * a,
const GstNvEncoderDeviceCaps * b, GstNvEncoderDeviceCaps * merged)
{
GstNvEncoderDeviceCaps caps;
#define SELECT_MAX(value) G_STMT_START { \
caps.value = MAX (a->value, b->value); \
} G_STMT_END
#define SELECT_MIN(value) G_STMT_START { \
caps.value = MAX (MIN (a->value, b->value), 1); \
} G_STMT_END
SELECT_MAX (max_bframes);
SELECT_MAX (ratecontrol_modes);
SELECT_MAX (field_encoding);
SELECT_MAX (monochrome);
SELECT_MAX (fmo);
SELECT_MAX (qpelmv);
SELECT_MAX (bdirect_mode);
SELECT_MAX (cabac);
SELECT_MAX (adaptive_transform);
SELECT_MAX (stereo_mvc);
SELECT_MAX (temoral_layers);
SELECT_MAX (hierarchical_pframes);
SELECT_MAX (hierarchical_bframes);
SELECT_MAX (level_max);
SELECT_MAX (level_min);
SELECT_MAX (separate_colour_plane);
SELECT_MAX (width_max);
SELECT_MAX (height_max);
SELECT_MAX (temporal_svc);
SELECT_MAX (dyn_res_change);
SELECT_MAX (dyn_bitrate_change);
SELECT_MAX (dyn_force_constqp);
SELECT_MAX (dyn_rcmode_change);
SELECT_MAX (subframe_readback);
SELECT_MAX (constrained_encoding);
SELECT_MAX (intra_refresh);
SELECT_MAX (custom_vbv_buf_size);
SELECT_MAX (dynamic_slice_mode);
SELECT_MAX (ref_pic_invalidation);
SELECT_MAX (preproc_support);
SELECT_MAX (async_encoding_support);
SELECT_MAX (mb_num_max);
SELECT_MAX (mb_per_sec_max);
SELECT_MAX (yuv444_encode);
SELECT_MAX (lossless_encode);
SELECT_MAX (sao);
SELECT_MAX (meonly_mode);
SELECT_MAX (lookahead);
SELECT_MAX (temporal_aq);
SELECT_MAX (supports_10bit_encode);
SELECT_MAX (num_max_ltr_frames);
SELECT_MAX (weighted_prediction);
SELECT_MAX (bframe_ref_mode);
SELECT_MAX (emphasis_level_map);
SELECT_MIN (width_min);
SELECT_MIN (height_min);
SELECT_MAX (multiple_ref_frames);
#undef SELECT_MAX
#undef SELECT_MIN
*merged = caps;
}
gboolean
_gst_nv_enc_result (NVENCSTATUS status, GObject * self, const gchar * file,
const gchar * function, gint line)
{
if (status == NV_ENC_SUCCESS)
return TRUE;
#ifndef GST_DISABLE_GST_DEBUG
const gchar *status_str = nvenc_status_to_string (status);
gst_debug_log (GST_CAT_DEFAULT, GST_LEVEL_ERROR, file, function,
line, self, "NvEnc API call failed: 0x%x, %s",
(guint) status, status_str);
#endif
return FALSE;
}