From 60735deded911d2a537fe2ad8e85e75de95b9daa Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Wed, 16 Mar 2022 04:58:16 +0900 Subject: [PATCH] nvcodec: Add new Direct3D11/CUDA mode encoder implementation Adding new encoder elements nvd3d11{h264,h265}enc for Direct3D11 input support and re-written nvcuda{h264,h265}enc elements. Newly writeen elements have some differences compared with old nv{h264,h265}enc including non-backward compatible changes. * RGBA is not a supported input format any more: New elements will support only YUV formats to avoid implicit conversion done by hardware. Ideally it should be done by upstream element in order to have more control on it. Moreover, RGBA support can cause redundant RGBA -> YUV conversion if multiple encoders are used for the same RGBA input * Subsampled planar format support is dropped: I420 and YV12 format are not supported formats for Direct3D11. Although it's supported in CUDA mode, it's not a hardware friendly memory layout and it will waste GPU memory since UV planes will have large padding due to the memory layout requirement of NVENC. * GL support is dropped: Similar to the RGBA case, GL support in encoder would be suboptimal if GL input is used by multiple encoders, because each encoder will copy GL memory into CUDA memory. Upstream cudaupload element can be used for GL <-> CUDA interop instead. * No more pre-allocation of encoder input surfaces. New implementation will use input CUDA memory without copy (zero-copy) or will copy into a NVENC's input buffer struct in case of system memory input. Part-of: --- .../gst-plugins-bad/sys/nvcodec/gstnvenc.c | 14 + .../gst-plugins-bad/sys/nvcodec/gstnvenc.h | 4 + .../sys/nvcodec/gstnvencoder.cpp | 2000 +++++++++++++++++ .../sys/nvcodec/gstnvencoder.h | 173 ++ .../sys/nvcodec/gstnvh264encoder.cpp | 1928 ++++++++++++++++ .../sys/nvcodec/gstnvh264encoder.h | 37 + .../sys/nvcodec/gstnvh265encoder.cpp | 1939 ++++++++++++++++ .../sys/nvcodec/gstnvh265encoder.h | 36 + .../gst-plugins-bad/sys/nvcodec/meson.build | 28 +- .../gst-plugins-bad/sys/nvcodec/plugin.c | 53 +- .../tests/examples/nvcodec/nvcodec-kb.c | 167 +- .../tests/examples/nvcodec/nvcodec.c | 24 +- 12 files changed, 6367 insertions(+), 36 deletions(-) create mode 100644 subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.cpp create mode 100644 subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.h create mode 100644 subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264encoder.cpp create mode 100644 subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264encoder.h create mode 100644 subprojects/gst-plugins-bad/sys/nvcodec/gstnvh265encoder.cpp create mode 100644 subprojects/gst-plugins-bad/sys/nvcodec/gstnvh265encoder.h diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvenc.c b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvenc.c index 19637671ad..7cbd10f50d 100644 --- a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvenc.c +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvenc.c @@ -261,6 +261,20 @@ NvEncEncodePicture (void *encoder, NV_ENC_PIC_PARAMS * pic_params) return nvenc_api.nvEncEncodePicture (encoder, pic_params); } +NVENCSTATUS NVENCAPI +NvEncRegisterAsyncEvent (void *encoder, NV_ENC_EVENT_PARAMS * event_params) +{ + g_assert (nvenc_api.nvEncRegisterAsyncEvent != NULL); + return nvenc_api.nvEncRegisterAsyncEvent (encoder, event_params); +} + +NVENCSTATUS NVENCAPI +NvEncUnregisterAsyncEvent (void *encoder, NV_ENC_EVENT_PARAMS * event_params) +{ + g_assert (nvenc_api.nvEncUnregisterAsyncEvent != NULL); + return nvenc_api.nvEncUnregisterAsyncEvent (encoder, event_params); +} + gboolean gst_nvenc_cmp_guid (GUID g1, GUID g2) { diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvenc.h b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvenc.h index cd7af19ae0..4437386940 100644 --- a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvenc.h +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvenc.h @@ -26,6 +26,8 @@ #include "gstcudaloader.h" #include "nvEncodeAPI.h" +G_BEGIN_DECLS + gboolean gst_nvenc_cmp_guid (GUID g1, GUID g2); NV_ENC_BUFFER_FORMAT gst_nvenc_get_nv_buffer_format (GstVideoFormat fmt); @@ -89,4 +91,6 @@ guint32 gst_nvenc_get_open_encode_session_ex_params_version (voi gboolean gst_nvenc_load_library (guint * api_major_ver, guint * api_minor_ver); +G_END_DECLS + #endif /* __GST_NVENC_H_INCLUDED__ */ diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.cpp b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.cpp new file mode 100644 index 0000000000..c2cd6c8a21 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.cpp @@ -0,0 +1,2000 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * 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 "gstcudautils.h" +#include "gstcudamemory.h" +#include "gstcudabufferpool.h" +#include + +#ifdef HAVE_NVCODEC_GST_D3D11 +#include +#endif + +#ifdef G_OS_WIN32 +#include + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ +#endif + +GST_DEBUG_CATEGORY_STATIC (gst_nv_encoder_debug); +#define GST_CAT_DEFAULT gst_nv_encoder_debug + +#define GET_LOCK(e) (&(GST_NV_ENCODER_CAST(e)->priv->lock)) +#define GST_NV_ENCODER_LOCK(e) G_STMT_START { \ + GST_TRACE_OBJECT (e, "Locking from thread %p", g_thread_self ()); \ + g_mutex_lock(GET_LOCK(e)); \ + GST_TRACE_OBJECT (e, "Locked from thread %p", g_thread_self ()); \ +} G_STMT_END + +#define GST_NV_ENCODER_UNLOCK(e) G_STMT_START { \ + GST_TRACE_OBJECT (e, "Unlocking from thread %p", g_thread_self ()); \ + g_mutex_unlock(GET_LOCK(e)); \ +} G_STMT_END + +struct _GstNvEncoderPrivate +{ + GstCudaContext *context; +#ifdef HAVE_NVCODEC_GST_D3D11 + GstD3D11Device *device; +#endif + + gint64 dxgi_adapter_luid; + guint cuda_device_id; + gboolean d3d11_mode; + + NV_ENC_INITIALIZE_PARAMS init_params; + NV_ENC_CONFIG config; + gpointer session; + + GstVideoCodecState *input_state; + + GstBufferPool *internal_pool; + + GstClockTime dts_offset; + + /* Array of GstNvEncoderTask, holding ownership */ + GArray *task_pool; + + GQueue free_tasks; + GQueue output_tasks; + + GMutex lock; + GCond cond; + + GThread *encoding_thread; + + GstFlowReturn last_flow; +}; + +#define gst_nv_encoder_parent_class parent_class +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GstNvEncoder, gst_nv_encoder, + GST_TYPE_VIDEO_ENCODER); + +static void gst_nv_encoder_finalize (GObject * object); +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_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 void gst_nv_encoder_task_clear (GstNvEncoderTask * task); + +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; + + 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_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); + + GST_DEBUG_CATEGORY_INIT (gst_nv_encoder_debug, "nvencoder", 0, "nvencoder"); +} + +static void +gst_nv_encoder_init (GstNvEncoder * self) +{ + GstNvEncoderPrivate *priv; + + self->priv = priv = (GstNvEncoderPrivate *) + gst_nv_encoder_get_instance_private (self); + + priv->task_pool = g_array_new (FALSE, TRUE, sizeof (GstNvEncoderTask)); + g_array_set_clear_func (priv->task_pool, + (GDestroyNotify) gst_nv_encoder_task_clear); + + g_queue_init (&priv->free_tasks); + g_queue_init (&priv->output_tasks); + + g_mutex_init (&priv->lock); + g_cond_init (&priv->cond); + + gst_video_encoder_set_min_pts (GST_VIDEO_ENCODER (self), + GST_SECOND * 60 * 60 * 1000); +} + +static void +gst_nv_encoder_finalize (GObject * object) +{ + GstNvEncoder *self = GST_NV_ENCODER (object); + GstNvEncoderPrivate *priv = self->priv; + + g_array_unref (priv->task_pool); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_nv_encoder_set_context (GstElement * element, GstContext * context) +{ + GstNvEncoder *self = GST_NV_ENCODER (element); + GstNvEncoderPrivate *priv = self->priv; + +#ifdef HAVE_NVCODEC_GST_D3D11 + if (priv->d3d11_mode) { + gst_d3d11_handle_set_context_for_adapter_luid (element, + context, priv->dxgi_adapter_luid, &priv->device); + } +#endif + + gst_cuda_handle_set_context (element, context, priv->cuda_device_id, + &priv->context); + + 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"); + + g_array_set_size (priv->task_pool, 0); + + if (priv->internal_pool) { + gst_buffer_pool_set_active (priv->internal_pool, FALSE); + gst_clear_object (&priv->internal_pool); + } + + if (priv->session) { + NvEncDestroyEncoder (priv->session); + priv->session = NULL; + } + + g_queue_clear (&priv->free_tasks); + g_queue_clear (&priv->output_tasks); + + priv->last_flow = GST_FLOW_OK; + + return TRUE; +} + +static gboolean +gst_nv_encoder_device_lock (GstNvEncoder * self) +{ + GstNvEncoderPrivate *priv = self->priv; + +#ifdef HAVE_NVCODEC_GST_D3D11 + if (priv->d3d11_mode) { + gst_d3d11_device_lock (priv->device); + return TRUE; + } +#endif + + return gst_cuda_context_push (priv->context); +} + +static gboolean +gst_nv_encoder_device_unlock (GstNvEncoder * self) +{ +#ifdef HAVE_NVCODEC_GST_D3D11 + GstNvEncoderPrivate *priv = self->priv; + + if (priv->d3d11_mode) { + gst_d3d11_device_unlock (priv->device); + return TRUE; + } +#endif + + return gst_cuda_context_pop (NULL); +} + +static GstFlowReturn +gst_nv_encoder_get_free_task (GstNvEncoder * self, GstNvEncoderTask ** task, + gboolean check_last_flow) +{ + GstNvEncoderPrivate *priv = self->priv; + GstFlowReturn ret = GST_FLOW_OK; + GstNvEncoderTask *free_task = NULL; + + GST_NV_ENCODER_LOCK (self); + if (check_last_flow) { + if (priv->last_flow != GST_FLOW_OK) { + ret = priv->last_flow; + GST_NV_ENCODER_UNLOCK (self); + return ret; + } + + while (priv->last_flow == GST_FLOW_OK && (free_task = (GstNvEncoderTask *) + g_queue_pop_head (&priv->free_tasks)) == NULL) { + g_cond_wait (&priv->cond, &priv->lock); + } + + ret = priv->last_flow; + if (ret != GST_FLOW_OK && free_task) { + g_queue_push_tail (&priv->free_tasks, free_task); + free_task = NULL; + } + } else { + while ((free_task = (GstNvEncoderTask *) + g_queue_pop_head (&priv->free_tasks)) == NULL) + g_cond_wait (&priv->cond, &priv->lock); + } + GST_NV_ENCODER_UNLOCK (self); + + *task = free_task; + + return ret; +} + +static gboolean +gst_nv_encoder_drain (GstNvEncoder * self, gboolean locked) +{ + GstNvEncoderPrivate *priv = self->priv; + NV_ENC_PIC_PARAMS pic_params = { 0, }; + NVENCSTATUS status; + GstNvEncoderTask *task; + + if (!priv->session || !priv->encoding_thread) + return TRUE; + + GST_DEBUG_OBJECT (self, "Drain"); + + if (locked) + GST_VIDEO_ENCODER_STREAM_UNLOCK (self); + + gst_nv_encoder_get_free_task (self, &task, FALSE); + + task->is_eos = TRUE; + + pic_params.version = gst_nvenc_get_pic_params_version (); + pic_params.encodePicFlags = NV_ENC_PIC_FLAG_EOS; + pic_params.completionEvent = task->event_handle; + + gst_nv_encoder_device_lock (self); + status = NvEncEncodePicture (priv->session, &pic_params); + if (status != NV_ENC_SUCCESS) { + GST_DEBUG_OBJECT (self, "Drain returned status %" GST_NVENC_STATUS_FORMAT, + GST_NVENC_STATUS_ARGS (status)); +#ifdef G_OS_WIN32 + if (task->event_handle) { + SetEvent (task->event_handle); + } +#endif + } + gst_nv_encoder_device_unlock (self); + + GST_NV_ENCODER_LOCK (self); + g_queue_push_tail (&priv->output_tasks, task); + g_cond_broadcast (&priv->cond); + GST_NV_ENCODER_UNLOCK (self); + + g_clear_pointer (&priv->encoding_thread, g_thread_join); + gst_nv_encoder_reset (self); + + if (locked) + GST_VIDEO_ENCODER_STREAM_LOCK (self); + + return TRUE; +} + +#ifdef HAVE_NVCODEC_GST_D3D11 +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; + +#ifdef HAVE_NVCODEC_GST_D3D11 + if (priv->d3d11_mode) { + return gst_nv_encoder_open_d3d11_device (self); + } +#endif + + 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; + } + + return TRUE; +} + +static gboolean +gst_nv_encoder_close (GstVideoEncoder * encoder) +{ + GstNvEncoder *self = GST_NV_ENCODER (encoder); + GstNvEncoderPrivate *priv = self->priv; + + gst_clear_object (&priv->context); +#ifdef HAVE_NVCODEC_GST_D3D11 + gst_clear_object (&priv->device); +#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); + + g_clear_pointer (&priv->input_state, gst_video_codec_state_unref); + + return TRUE; +} + +static gboolean +gst_nv_encoder_handle_context_query (GstNvEncoder * self, GstQuery * query) +{ + GstNvEncoderPrivate *priv = self->priv; + +#ifdef HAVE_NVCODEC_GST_D3D11 + if (priv->d3d11_mode) { + return gst_d3d11_handle_context_query (GST_ELEMENT (self), + query, priv->device); + } +#endif + + return gst_cuda_handle_context_query (GST_ELEMENT (self), + query, priv->context); +} + +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 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; + + 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); +#ifdef HAVE_NVCODEC_GST_D3D11 + if (priv->d3d11_mode && 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); + } +#endif + + if (!priv->d3d11_mode && 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); + } + + 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); + + min_buffers = gst_nv_encoder_get_task_size (GST_NV_ENCODER (self)); + + size = GST_VIDEO_INFO_SIZE (&info); + gst_buffer_pool_config_set_params (config, caps, size, min_buffers, 0); + + 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; +} + +/* called with lock */ +static void +gst_nv_encoder_task_reset (GstNvEncoder * self, GstNvEncoderTask * task) +{ + GstNvEncoderPrivate *priv = self->priv; + + if (!task) + return; + + if (task->buffer) { + gst_nv_encoder_device_lock (self); + if (priv->session) { + NvEncUnmapInputResource (priv->session, + task->mapped_resource.mappedResource); + NvEncUnregisterResource (priv->session, + task->register_resource.registeredResource); + } + gst_nv_encoder_device_unlock (self); + + gst_buffer_unmap (task->buffer, &task->map_info); + gst_clear_buffer (&task->buffer); + } +#ifdef G_OS_WIN32 + if (task->event_handle) + ResetEvent (task->event_handle); +#endif + + task->is_eos = FALSE; + + g_queue_push_head (&priv->free_tasks, task); +} + +static gboolean +gst_nv_encoder_create_event_handle (GstNvEncoder * self, gpointer session, + gpointer * event_handle) +{ +#ifdef G_OS_WIN32 + NV_ENC_EVENT_PARAMS event_params = { 0, }; + NVENCSTATUS status; + + event_params.version = gst_nvenc_get_event_params_version (); + event_params.completionEvent = CreateEvent (NULL, FALSE, FALSE, NULL); + status = NvEncRegisterAsyncEvent (session, &event_params); + + if (status != NV_ENC_SUCCESS) { + GST_ERROR_OBJECT (self, + "Failed to register async event handle, status %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + CloseHandle (event_params.completionEvent); + return FALSE; + } + + *event_handle = event_params.completionEvent; +#endif + + return TRUE; +} + +static gboolean +gst_d3d11_encoder_wait_for_event_handle (GstNvEncoder * self, + gpointer event_handle) +{ +#ifdef G_OS_WIN32 + /* NVCODEC SDK uses 20s */ + if (WaitForSingleObject (event_handle, 20000) == WAIT_FAILED) { + GST_ERROR_OBJECT (self, "Failed to wait for completion event"); + return FALSE; + } +#endif + + return TRUE; +} + +static void +gst_nv_encoder_destroy_event_handle (GstNvEncoder * self, gpointer session, + gpointer event_handle) +{ +#ifdef G_OS_WIN32 + NV_ENC_EVENT_PARAMS event_params = { 0, }; + NVENCSTATUS status; + + event_params.version = gst_nvenc_get_event_params_version (); + event_params.completionEvent = event_handle; + status = NvEncUnregisterAsyncEvent (session, &event_params); + CloseHandle (event_handle); + + if (status != NV_ENC_SUCCESS) { + GST_ERROR_OBJECT (self, + "Failed to unregister async event handle, status %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + } +#endif +} + +static void +gst_nv_encoder_task_clear (GstNvEncoderTask * task) +{ + GstNvEncoder *self; + GstNvEncoderPrivate *priv; + + if (!task) + return; + + self = task->encoder; + priv = self->priv; + + if (priv->session) { + gst_nv_encoder_device_lock (self); + if (task->buffer) { + NvEncUnmapInputResource (priv->session, + task->mapped_resource.mappedResource); + NvEncUnregisterResource (priv->session, + task->register_resource.registeredResource); + } + if (task->output_ptr) + NvEncDestroyBitstreamBuffer (priv->session, task->output_ptr); + if (task->input_buffer.inputBuffer) + NvEncDestroyInputBuffer (priv->session, task->input_buffer.inputBuffer); + if (task->event_handle) { + gst_nv_encoder_destroy_event_handle (self, priv->session, + task->event_handle); + } + + gst_nv_encoder_device_unlock (self); + } + + if (task->buffer) { + gst_buffer_unmap (task->buffer, &task->map_info); + gst_clear_buffer (&task->buffer); + } + + memset (task, 0, sizeof (GstNvEncoderTask)); +} + +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 GstFlowReturn +gst_nv_encoder_encode_frame (GstNvEncoder * self, + GstVideoCodecFrame * frame, GstNvEncoderTask * task) +{ + GstNvEncoderPrivate *priv = self->priv; + NV_ENC_PIC_PARAMS pic_params = { 0, }; + NVENCSTATUS status; + guint retry_count = 0; + const guint retry_threshold = 100; + + pic_params.version = gst_nvenc_get_pic_params_version (); + if (task->buffer) { + pic_params.inputWidth = task->register_resource.width; + pic_params.inputHeight = task->register_resource.height; + pic_params.inputPitch = task->register_resource.pitch; + pic_params.inputBuffer = task->mapped_resource.mappedResource; + pic_params.bufferFmt = task->mapped_resource.mappedBufferFmt; + } else { + pic_params.inputWidth = task->input_buffer.width; + pic_params.inputHeight = task->input_buffer.height; + pic_params.inputPitch = task->lk_input_buffer.pitch; + pic_params.inputBuffer = task->input_buffer.inputBuffer; + pic_params.bufferFmt = task->input_buffer.bufferFmt; + } + + pic_params.frameIdx = frame->system_frame_number; + pic_params.inputTimeStamp = frame->pts; + pic_params.inputDuration = frame->duration; + pic_params.outputBitstream = task->output_ptr; + pic_params.completionEvent = task->event_handle; + pic_params.pictureStruct = gst_nv_encoder_get_pic_struct (self, task->buffer); + if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (frame)) + pic_params.encodePicFlags = NV_ENC_PIC_FLAG_FORCEIDR; + + do { + gst_nv_encoder_device_lock (self); + status = NvEncEncodePicture (priv->session, &pic_params); + gst_nv_encoder_device_unlock (self); + + if (status == NV_ENC_ERR_ENCODER_BUSY) { + if (retry_count < 100) { + GST_DEBUG_OBJECT (self, "GPU is busy, retry count (%d/%d)", retry_count, + retry_threshold); + retry_count++; + + /* Magic number 1ms */ + g_usleep (1000); + continue; + } else { + GST_ERROR_OBJECT (self, "GPU is keep busy, give up"); + break; + } + } + + break; + } while (TRUE); + + GST_NV_ENCODER_LOCK (self); + if (status != NV_ENC_SUCCESS && status != NV_ENC_ERR_NEED_MORE_INPUT) { + GST_ERROR_OBJECT (self, "Encode return %" GST_NVENC_STATUS_FORMAT, + GST_NVENC_STATUS_ARGS (status)); + gst_nv_encoder_task_reset (self, task); + GST_NV_ENCODER_UNLOCK (self); + + return GST_FLOW_ERROR; + } + + gst_video_codec_frame_set_user_data (frame, task, NULL); + g_queue_push_tail (&priv->output_tasks, task); + g_cond_broadcast (&priv->cond); + GST_NV_ENCODER_UNLOCK (self); + + return GST_FLOW_OK; +} + +static GstVideoCodecFrame * +gst_nv_encoder_find_output_frame (GstVideoEncoder * self, + GstNvEncoderTask * 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; + GstNvEncoderTask *other = (GstNvEncoderTask *) + 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 gpointer +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; + GstNvEncoderTask *task = NULL; + + do { + NV_ENC_LOCK_BITSTREAM bitstream = { 0, }; + NVENCSTATUS status; + GstVideoCodecFrame *frame; + GstFlowReturn ret; + + GST_NV_ENCODER_LOCK (self); + while ((task = (GstNvEncoderTask *) + g_queue_pop_head (&priv->output_tasks)) == NULL) { + g_cond_wait (&priv->cond, &priv->lock); + } + GST_NV_ENCODER_UNLOCK (self); + + if (task->event_handle) { + if (!gst_d3d11_encoder_wait_for_event_handle (self, task->event_handle)) { + GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL), + ("Failed to wait for event signal")); + goto error; + } + } + + if (task->is_eos) { + GST_INFO_OBJECT (self, "Got EOS packet"); + + GST_NV_ENCODER_LOCK (self); + gst_nv_encoder_task_reset (self, task); + g_cond_broadcast (&priv->cond); + GST_NV_ENCODER_UNLOCK (self); + + goto exit_thread; + } + + frame = gst_nv_encoder_find_output_frame (encoder, task); + if (!frame) { + GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL), + ("Failed to find associated codec frame")); + goto error; + } + + if (!gst_nv_encoder_device_lock (self)) { + GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL), + ("Failed to lock device")); + goto error; + } + + bitstream.version = gst_nvenc_get_lock_bitstream_version (); + bitstream.outputBitstream = task->output_ptr; + + status = NvEncLockBitstream (priv->session, &bitstream); + if (status != NV_ENC_SUCCESS) { + gst_nv_encoder_device_unlock (self); + GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL), + ("Failed to lock bitstream, status: %" GST_NVENC_STATUS_FORMAT, + GST_NVENC_STATUS_ARGS (status))); + goto error; + } + + 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); + + NvEncUnlockBitstream (priv->session, task->output_ptr); + gst_nv_encoder_device_unlock (self); + + frame->dts = frame->pts - priv->dts_offset; + frame->pts = bitstream.outputTimeStamp; + frame->duration = bitstream.outputDuration; + + ret = gst_video_encoder_finish_frame (encoder, frame); + if (ret != GST_FLOW_OK) { + GST_INFO_OBJECT (self, + "Finish frame returned %s", gst_flow_get_name (ret)); + } + + GST_NV_ENCODER_LOCK (self); + gst_nv_encoder_task_reset (self, task); + priv->last_flow = ret; + g_cond_broadcast (&priv->cond); + GST_NV_ENCODER_UNLOCK (self); + + if (ret != GST_FLOW_OK) { + GST_INFO_OBJECT (self, "Push returned %s", gst_flow_get_name (ret)); + goto exit_thread; + } + } while (TRUE); + +exit_thread: + { + GST_INFO_OBJECT (self, "Exiting thread"); + + return NULL; + + } +error: + { + GST_NV_ENCODER_LOCK (self); + gst_nv_encoder_task_reset (self, task); + priv->last_flow = GST_FLOW_ERROR; + g_cond_broadcast (&priv->cond); + GST_NV_ENCODER_UNLOCK (self); + + goto exit_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, gpointer * session) +{ + 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 (); + NVENCSTATUS status; + +#ifdef HAVE_NVCODEC_GST_D3D11 + if (priv->d3d11_mode) { + session_params.deviceType = NV_ENC_DEVICE_TYPE_DIRECTX; + session_params.device = gst_d3d11_device_get_device_handle (priv->device); + } else +#endif + { + session_params.deviceType = NV_ENC_DEVICE_TYPE_CUDA; + session_params.device = gst_cuda_context_get_handle (priv->context); + } + + status = NvEncOpenEncodeSessionEx (&session_params, session); + if (status != NV_ENC_SUCCESS) { + GST_ERROR_OBJECT (self, "Failed to open session, status: %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + return FALSE; + } + + return TRUE; +} + +#ifdef HAVE_NVCODEC_GST_D3D11 +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, + (GstD3D11AllocationFlags) 0, 0); + params->desc[0].MiscFlags = 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; +#ifdef HAVE_NVCODEC_GST_D3D11 + if (priv->d3d11_mode) + return gst_nv_encoder_create_d3d11_pool (self, state); +#endif + + pool = gst_cuda_buffer_pool_new (priv->context); + + 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 (!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) +{ + 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; + guint i; + + gst_nv_encoder_reset (self); + + memset (&priv->init_params, 0, sizeof (NV_ENC_INITIALIZE_PARAMS)); + memset (&priv->config, 0, sizeof (NV_ENC_CONFIG)); + + 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, &priv->session)) { + GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL), + ("Failed to open session")); + goto error; + } + + if (!klass->set_format (self, state, priv->session, &priv->init_params, + &priv->config)) { + GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL), ("Failed to set format")); + goto error; + } + + priv->init_params.encodeConfig = &priv->config; + status = NvEncInitializeEncoder (priv->session, &priv->init_params); + if (status != NV_ENC_SUCCESS) { + GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL), + ("Failed to init encoder, status: %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status))); + goto error; + } + + task_pool_size = gst_nv_encoder_calculate_task_pool_size (self, + &priv->config); + g_array_set_size (priv->task_pool, task_pool_size); + + for (i = 0; i < task_pool_size; i++) { + NV_ENC_CREATE_BITSTREAM_BUFFER buffer_params = { 0, }; + GstNvEncoderTask *task = (GstNvEncoderTask *) + & g_array_index (priv->task_pool, GstNvEncoderTask, i); + + task->encoder = self; + + buffer_params.version = gst_nvenc_get_create_bitstream_buffer_version (); + status = NvEncCreateBitstreamBuffer (priv->session, &buffer_params); + + if (status != NV_ENC_SUCCESS) { + GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL), + ("Failed to create bitstream buffer, status: %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status))); + goto error; + } + + task->output_ptr = buffer_params.bitstreamBuffer; + + if (priv->init_params.enableEncodeAsync) { + if (!gst_nv_encoder_create_event_handle (self, + priv->session, &task->event_handle)) { + GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL), + ("Failed to create async event handle")); + goto error; + } + } + + g_queue_push_tail (&priv->free_tasks, task); + } + gst_nv_encoder_device_unlock (self); + + if (!klass->set_output_state (self, priv->input_state, priv->session)) { + GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL), + ("Failed to set output state")); + gst_nv_encoder_reset (self); + return FALSE; + } + + priv->encoding_thread = g_thread_new ("GstNvEncoderThread", + (GThreadFunc) 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 * priv->task_pool->len; + 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->session) { + GST_WARNING_OBJECT (self, + "Encoding session was not configured, open session"); + gst_nv_encoder_drain (self, TRUE); + + return gst_nv_encoder_init_session (self); + } + + params.version = gst_nvenc_get_reconfigure_params_version (); + params.reInitEncodeParams = priv->init_params; + params.reInitEncodeParams.encodeConfig = &priv->config; + + status = NvEncReconfigureEncoder (priv->session, ¶ms); + if (status != NV_ENC_SUCCESS) { + GST_WARNING_OBJECT (self, "Failed to reconfigure encoder, status %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + gst_nv_encoder_drain (self, TRUE); + + return gst_nv_encoder_init_session (self); + } + + 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; + + return gst_nv_encoder_init_session (self); +} + +static NV_ENC_BUFFER_FORMAT +gst_nv_encoder_get_buffer_format (GstNvEncoder * self, GstVideoFormat format) +{ + switch (format) { + case GST_VIDEO_FORMAT_NV12: + return NV_ENC_BUFFER_FORMAT_NV12; + case GST_VIDEO_FORMAT_Y444: + return NV_ENC_BUFFER_FORMAT_YUV444; + case GST_VIDEO_FORMAT_P010_10LE: + return NV_ENC_BUFFER_FORMAT_YUV420_10BIT; + case GST_VIDEO_FORMAT_Y444_16LE: + return NV_ENC_BUFFER_FORMAT_YUV444_10BIT; + default: + GST_ERROR_OBJECT (self, "Unexpected format %s", + gst_video_format_to_string (format)); + g_assert_not_reached (); + break; + } + + return NV_ENC_BUFFER_FORMAT_UNDEFINED; +} + +static GstFlowReturn +gst_nv_encoder_copy_system (GstNvEncoder * self, const GstVideoInfo * info, + GstBuffer * buffer, gpointer session, GstNvEncoderTask * task) +{ + NVENCSTATUS status; + GstVideoFrame frame; + guint8 *dst_data; + NV_ENC_BUFFER_FORMAT format; + + format = + gst_nv_encoder_get_buffer_format (self, GST_VIDEO_INFO_FORMAT (info)); + if (format == NV_ENC_BUFFER_FORMAT_UNDEFINED) + return GST_FLOW_ERROR; + + if (!gst_video_frame_map (&frame, info, buffer, GST_MAP_READ)) { + GST_ERROR_OBJECT (self, "Failed to map buffer"); + return GST_FLOW_ERROR; + } + + if (!task->input_buffer.inputBuffer) { + NV_ENC_CREATE_INPUT_BUFFER input_buffer = { 0, }; + input_buffer.version = gst_nvenc_get_create_input_buffer_version (); + input_buffer.width = info->width; + input_buffer.height = info->height; + input_buffer.bufferFmt = format; + + status = NvEncCreateInputBuffer (session, &input_buffer); + if (status != NV_ENC_SUCCESS) { + GST_ERROR_OBJECT (self, "Failed to create input buffer, status %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + gst_video_frame_unmap (&frame); + return GST_FLOW_ERROR; + } + + task->input_buffer = input_buffer; + } + + task->lk_input_buffer.version = gst_nvenc_get_lock_input_buffer_version (); + task->lk_input_buffer.inputBuffer = task->input_buffer.inputBuffer; + status = NvEncLockInputBuffer (session, &task->lk_input_buffer); + if (status != NV_ENC_SUCCESS) { + GST_ERROR_OBJECT (self, "Failed to lock input buffer, status %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + gst_video_frame_unmap (&frame); + return GST_FLOW_ERROR; + } + + dst_data = (guint8 *) task->lk_input_buffer.bufferDataPtr; + + 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 += task->lk_input_buffer.pitch; + src_data += stride; + } + } + + NvEncUnlockInputBuffer (session, task->input_buffer.inputBuffer); + gst_video_frame_unmap (&frame); + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_nv_encoder_prepare_task_input_cuda (GstNvEncoder * self, + const GstVideoInfo * info, GstBuffer * buffer, gpointer session, + GstNvEncoderTask * task) +{ + GstNvEncoderPrivate *priv = self->priv; + GstMemory *mem; + GstCudaMemory *cmem; + NVENCSTATUS status; + + mem = gst_buffer_peek_memory (buffer, 0); + 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, session, 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, session, task); + } + + task->buffer = gst_buffer_ref (buffer); + if (!gst_buffer_map (task->buffer, &task->map_info, + (GstMapFlags) (GST_MAP_READ | GST_MAP_CUDA))) { + GST_ERROR_OBJECT (self, "Failed to map buffer"); + gst_clear_buffer (&task->buffer); + + return GST_FLOW_ERROR; + } + + cmem = (GstCudaMemory *) gst_buffer_peek_memory (task->buffer, 0); + + task->register_resource.version = gst_nvenc_get_register_resource_version (); + task->register_resource.resourceType = + NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR; + task->register_resource.width = cmem->info.width; + task->register_resource.height = cmem->info.height; + task->register_resource.pitch = cmem->info.stride[0]; + task->register_resource.resourceToRegister = task->map_info.data; + task->register_resource.bufferFormat = + gst_nv_encoder_get_buffer_format (self, GST_VIDEO_INFO_FORMAT (info)); + if (task->register_resource.bufferFormat == NV_ENC_BUFFER_FORMAT_UNDEFINED) + return GST_FLOW_ERROR; + + status = NvEncRegisterResource (session, &task->register_resource); + if (status != NV_ENC_SUCCESS) { + GST_ERROR_OBJECT (self, "Failed to register resource, status %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + + gst_buffer_unmap (task->buffer, &task->map_info); + gst_clear_buffer (&task->buffer); + + return GST_FLOW_ERROR; + } + + task->mapped_resource.version = gst_nvenc_get_map_input_resource_version (); + task->mapped_resource.registeredResource = + task->register_resource.registeredResource; + status = NvEncMapInputResource (session, &task->mapped_resource); + if (status != NV_ENC_SUCCESS) { + GST_ERROR_OBJECT (self, "Failed to map input resource, status %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + NvEncUnregisterResource (session, + task->register_resource.registeredResource); + + gst_buffer_unmap (task->buffer, &task->map_info); + gst_clear_buffer (&task->buffer); + + return GST_FLOW_ERROR; + } + + return GST_FLOW_OK; +} + +#ifdef HAVE_NVCODEC_GST_D3D11 +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; + ComPtr < ID3D11Query > query; + D3D11_QUERY_DESC query_desc; + BOOL sync_done = FALSE; + 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) { + query_desc.Query = D3D11_QUERY_EVENT; + query_desc.MiscFlags = 0; + + hr = device_handle->CreateQuery (&query_desc, &query); + if (!gst_d3d11_result (hr, device)) { + GST_ERROR_OBJECT (self, "Couldn't Create event query, hr: 0x%x", + (guint) hr); + goto error; + } + + gst_d3d11_device_lock (device); + } + + device_context->CopySubresourceRegion (dst_tex, 0, + 0, 0, 0, src_tex, subresource_idx, &src_box); + + if (shared) { + device_context->End (query.Get ()); + do { + hr = device_context->GetData (query.Get (), &sync_done, sizeof (BOOL), 0); + } while (!sync_done && (hr == S_OK || hr == S_FALSE)); + + if (!gst_d3d11_result (hr, device)) { + GST_ERROR_OBJECT (self, "Couldn't sync GPU operation, hr: 0x%x", + (guint) hr); + gst_d3d11_device_unlock (device); + 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, + const GstVideoInfo * info, GstBuffer * buffer, gpointer session, + GstBufferPool * pool, GstNvEncoderTask * task) +{ + GstNvEncoderPrivate *priv = self->priv; + GstMemory *mem; + GstD3D11Memory *dmem; + D3D11_TEXTURE2D_DESC desc; + NVENCSTATUS status; + + 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, session, 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, session, 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"); + task->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, session, task); + } + } + + if (!task->buffer) + task->buffer = gst_nv_encoder_upload_d3d11_frame (self, info, buffer, pool); + + if (!task->buffer) { + GST_ERROR_OBJECT (self, "Failed to upload buffer"); + return GST_FLOW_ERROR; + } + + if (!gst_buffer_map (task->buffer, &task->map_info, + (GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11))) { + GST_ERROR_OBJECT (self, "Failed to map buffer"); + gst_clear_buffer (&task->buffer); + + return GST_FLOW_ERROR; + } + + dmem = (GstD3D11Memory *) gst_buffer_peek_memory (task->buffer, 0); + gst_d3d11_memory_get_texture_desc (dmem, &desc); + + task->register_resource.version = gst_nvenc_get_register_resource_version (); + task->register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX; + task->register_resource.width = desc.Width; + task->register_resource.height = desc.Height; + switch (desc.Format) { + case DXGI_FORMAT_NV12: + task->register_resource.bufferFormat = NV_ENC_BUFFER_FORMAT_NV12; + break; + case DXGI_FORMAT_P010: + task->register_resource.bufferFormat = NV_ENC_BUFFER_FORMAT_YUV420_10BIT; + break; + default: + GST_ERROR_OBJECT (self, "Unexpected DXGI format %d", desc.Format); + g_assert_not_reached (); + return GST_FLOW_ERROR; + } + + task->register_resource.subResourceIndex = + gst_d3d11_memory_get_subresource_index (dmem); + task->register_resource.resourceToRegister = + gst_d3d11_memory_get_resource_handle (dmem); + + status = NvEncRegisterResource (session, &task->register_resource); + if (status != NV_ENC_SUCCESS) { + GST_ERROR_OBJECT (self, "Failed to register resource, status %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + + gst_buffer_unmap (task->buffer, &task->map_info); + gst_clear_buffer (&task->buffer); + + return GST_FLOW_ERROR; + } + + task->mapped_resource.version = gst_nvenc_get_map_input_resource_version (); + task->mapped_resource.registeredResource = + task->register_resource.registeredResource; + status = NvEncMapInputResource (session, &task->mapped_resource); + if (status != NV_ENC_SUCCESS) { + GST_ERROR_OBJECT (self, "Failed to map input resource, status %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + NvEncUnregisterResource (session, + task->register_resource.registeredResource); + + gst_buffer_unmap (task->buffer, &task->map_info); + gst_clear_buffer (&task->buffer); + + return GST_FLOW_ERROR; + } + + return GST_FLOW_OK; +} +#endif + +static GstFlowReturn +gst_nv_encoder_prepare_task_input (GstNvEncoder * self, + const GstVideoInfo * info, GstBuffer * buffer, gpointer session, + GstBufferPool * pool, GstNvEncoderTask * task) +{ +#ifdef HAVE_NVCODEC_GST_D3D11 + GstNvEncoderPrivate *priv = self->priv; + if (priv->d3d11_mode) { + return gst_nv_encoder_prepare_task_input_d3d11 (self, info, buffer, + session, pool, task); + } +#endif + + return gst_nv_encoder_prepare_task_input_cuda (self, info, buffer, + session, task); +} + +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; + GstNvEncoderTask *task = NULL; + GstNvEncoderReconfigure reconfig; + + GST_TRACE_OBJECT (self, "Handle frame"); + + GST_NV_ENCODER_LOCK (self); + ret = priv->last_flow; + GST_NV_ENCODER_UNLOCK (self); + + if (ret != GST_FLOW_OK) { + GST_INFO_OBJECT (self, "Last flow was %s", gst_flow_get_name (ret)); + gst_video_encoder_finish_frame (encoder, frame); + + return ret; + } + + if (!priv->session && !gst_nv_encoder_init_session (self)) { + 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)) { + 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); + ret = gst_nv_encoder_get_free_task (self, &task, TRUE); + GST_VIDEO_ENCODER_STREAM_LOCK (self); + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (self, "Last flow was %s", gst_flow_get_name (ret)); + gst_video_encoder_finish_frame (encoder, frame); + return ret; + } + + if (!gst_nv_encoder_device_lock (self)) { + GST_ERROR_OBJECT (self, "Failed to lock device"); + gst_video_encoder_finish_frame (encoder, frame); + + return GST_FLOW_ERROR; + } + + g_assert (task->buffer == NULL); + ret = gst_nv_encoder_prepare_task_input (self, &priv->input_state->info, + frame->input_buffer, priv->session, priv->internal_pool, task); + gst_nv_encoder_device_unlock (self); + + if (ret != GST_FLOW_OK) { + GST_ERROR_OBJECT (self, "Failed to upload frame"); + GST_NV_ENCODER_LOCK (self); + gst_nv_encoder_task_reset (self, task); + GST_NV_ENCODER_UNLOCK (self); + + gst_video_encoder_finish_frame (encoder, frame); + + return ret; + } + + ret = gst_nv_encoder_encode_frame (self, frame, task); + if (ret != GST_FLOW_OK) { + GST_ERROR_OBJECT (self, "Failed to encode frame"); + gst_video_encoder_finish_frame (encoder, frame); + + return ret; + } + + 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; +} + +guint +gst_nv_encoder_get_task_size (GstNvEncoder * encoder) +{ + g_return_val_if_fail (GST_IS_NV_ENCODER (encoder), 0); + + return encoder->priv->task_pool->len; +} + +void +gst_nv_encoder_set_cuda_device_id (GstNvEncoder * encoder, guint device_id) +{ + g_return_if_fail (GST_IS_NV_ENCODER (encoder)); + + encoder->priv->cuda_device_id = device_id; + encoder->priv->d3d11_mode = FALSE; +} + +void +gst_nv_encoder_set_dxgi_adapter_luid (GstNvEncoder * encoder, + gint64 adapter_luid) +{ + g_return_if_fail (GST_IS_NV_ENCODER (encoder)); + + encoder->priv->dxgi_adapter_luid = adapter_luid; + encoder->priv->d3d11_mode = TRUE; +} + +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; +} + +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_DEFAULT, "Default", "default"}, + {GST_NV_ENCODER_RC_MODE_CONSTQP, "Constant Quantization", "constqp"}, + {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_DEFAULT: + return NV_ENC_PARAMS_RC_VBR; + 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; +} + +const gchar * +gst_nv_encoder_status_to_string (NVENCSTATUS status) +{ +#define CASE(err) \ + case err: \ + return G_STRINGIFY (err); + + switch (status) { + CASE (NV_ENC_SUCCESS); + CASE (NV_ENC_ERR_NO_ENCODE_DEVICE); + CASE (NV_ENC_ERR_UNSUPPORTED_DEVICE); + CASE (NV_ENC_ERR_INVALID_ENCODERDEVICE); + CASE (NV_ENC_ERR_INVALID_DEVICE); + CASE (NV_ENC_ERR_DEVICE_NOT_EXIST); + CASE (NV_ENC_ERR_INVALID_PTR); + CASE (NV_ENC_ERR_INVALID_EVENT); + CASE (NV_ENC_ERR_INVALID_PARAM); + CASE (NV_ENC_ERR_INVALID_CALL); + CASE (NV_ENC_ERR_OUT_OF_MEMORY); + CASE (NV_ENC_ERR_ENCODER_NOT_INITIALIZED); + CASE (NV_ENC_ERR_UNSUPPORTED_PARAM); + CASE (NV_ENC_ERR_LOCK_BUSY); + CASE (NV_ENC_ERR_NOT_ENOUGH_BUFFER); + CASE (NV_ENC_ERR_INVALID_VERSION); + CASE (NV_ENC_ERR_MAP_FAILED); + CASE (NV_ENC_ERR_NEED_MORE_INPUT); + CASE (NV_ENC_ERR_ENCODER_BUSY); + CASE (NV_ENC_ERR_EVENT_NOT_REGISTERD); + CASE (NV_ENC_ERR_GENERIC); + CASE (NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY); + CASE (NV_ENC_ERR_UNIMPLEMENTED); + CASE (NV_ENC_ERR_RESOURCE_REGISTER_FAILED); + CASE (NV_ENC_ERR_RESOURCE_NOT_REGISTERED); + CASE (NV_ENC_ERR_RESOURCE_NOT_MAPPED); + default: + break; + } +#undef CASE + + return "Unknown"; +} diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.h b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.h new file mode 100644 index 0000000000..baaca93505 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.h @@ -0,0 +1,173 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * 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. + */ + +#pragma once + +#include +#include + +#ifdef HAVE_NVCODEC_GST_D3D11 +#include +#endif + +#include + +#include "nvEncodeAPI.h" +#include "gstnvenc.h" +#include "gstcudamemory.h" + +G_BEGIN_DECLS + +#define GST_TYPE_NV_ENCODER (gst_nv_encoder_get_type()) +#define GST_NV_ENCODER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_NV_ENCODER, GstNvEncoder)) +#define GST_NV_ENCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_NV_ENCODER, GstNvEncoderClass)) +#define GST_IS_NV_ENCODER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_NV_ENCODER)) +#define GST_IS_NV_ENCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_NV_ENCODER)) +#define GST_NV_ENCODER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_NV_ENCODER, GstNvEncoderClass)) +#define GST_NV_ENCODER_CAST(obj) ((GstNvEncoder *)obj) + +typedef struct _GstNvEncoder GstNvEncoder; +typedef struct _GstNvEncoderClass GstNvEncoderClass; +typedef struct _GstNvEncoderPrivate GstNvEncoderPrivate; + +typedef enum +{ + GST_NV_ENCODER_RECONFIGURE_NONE, + GST_NV_ENCODER_RECONFIGURE_BITRATE, + GST_NV_ENCODER_RECONFIGURE_FULL, +} GstNvEncoderReconfigure; + +#define GST_TYPE_NV_ENCODER_PRESET (gst_nv_encoder_preset_get_type()) +GType gst_nv_encoder_preset_get_type (void); +typedef enum +{ + GST_NV_ENCODER_PRESET_DEFAULT, + GST_NV_ENCODER_PRESET_HP, + GST_NV_ENCODER_PRESET_HQ, + GST_NV_ENCODER_PRESET_LOW_LATENCY_DEFAULT, + GST_NV_ENCODER_PRESET_LOW_LATENCY_HQ, + GST_NV_ENCODER_PRESET_LOW_LATENCY_HP, + GST_NV_ENCODER_PRESET_LOSSLESS_DEFAULT, + GST_NV_ENCODER_PRESET_LOSSLESS_HP, +} GstNvEncoderPreset; + +#define GST_TYPE_NV_ENCODER_RC_MODE (gst_nv_encoder_rc_mode_get_type()) +GType gst_nv_encoder_rc_mode_get_type (void); + +typedef enum +{ + GST_NV_ENCODER_RC_MODE_DEFAULT, + GST_NV_ENCODER_RC_MODE_CONSTQP, + GST_NV_ENCODER_RC_MODE_VBR, + GST_NV_ENCODER_RC_MODE_CBR, + GST_NV_ENCODER_RC_MODE_CBR_LOWDELAY_HQ, + GST_NV_ENCODER_RC_MODE_CBR_HQ, + GST_NV_ENCODER_RC_MODE_VBR_HQ, +} GstNvEncoderRCMode; + +typedef struct +{ + /* without ref */ + GstNvEncoder *encoder; + + /* Holds ownership */ + GstBuffer *buffer; + GstMapInfo map_info; + + NV_ENC_REGISTER_RESOURCE register_resource; + NV_ENC_MAP_INPUT_RESOURCE mapped_resource; + + /* Used when input resource cannot be registered */ + NV_ENC_CREATE_INPUT_BUFFER input_buffer; + NV_ENC_LOCK_INPUT_BUFFER lk_input_buffer; + + NV_ENC_OUTPUT_PTR output_ptr; + gpointer event_handle; + gboolean is_eos; +} GstNvEncoderTask; + +struct _GstNvEncoder +{ + GstVideoEncoder parent; + + GstNvEncoderPrivate *priv; +}; + +struct _GstNvEncoderClass +{ + GstVideoEncoderClass parent_class; + + gboolean (*set_format) (GstNvEncoder * encoder, + GstVideoCodecState * state, + gpointer session, + NV_ENC_INITIALIZE_PARAMS * init_params, + NV_ENC_CONFIG * config); + + gboolean (*set_output_state) (GstNvEncoder * encoder, + GstVideoCodecState * state, + gpointer session); + + GstBuffer * (*create_output_buffer) (GstNvEncoder * encoder, + NV_ENC_LOCK_BITSTREAM * bitstream); + + GstNvEncoderReconfigure (*check_reconfigure) (GstNvEncoder * encoder, + NV_ENC_CONFIG * config); +}; + +GType gst_nv_encoder_get_type (void); + +guint gst_nv_encoder_get_task_size (GstNvEncoder * encoder); + +const gchar * gst_nv_encoder_status_to_string (NVENCSTATUS status); +#define GST_NVENC_STATUS_FORMAT "s (%d)" +#define GST_NVENC_STATUS_ARGS(s) gst_nv_encoder_status_to_string (s), s + +void gst_nv_encoder_preset_to_guid (GstNvEncoderPreset preset, + GUID * guid); + +NV_ENC_PARAMS_RC_MODE gst_nv_encoder_rc_mode_to_native (GstNvEncoderRCMode rc_mode); + +void gst_nv_encoder_set_cuda_device_id (GstNvEncoder * encoder, + guint device_id); + +void gst_nv_encoder_set_dxgi_adapter_luid (GstNvEncoder * encoder, + gint64 adapter_luid); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstNvEncoder, gst_object_unref) + +G_END_DECLS + +#ifdef __cplusplus +#ifndef G_OS_WIN32 +inline bool is_equal_guid(const GUID & lhs, const GUID & rhs) +{ + return !!memcmp(&lhs, &rhs, sizeof (GUID)); +} + +inline bool operator==(const GUID & lhs, const GUID & rhs) +{ + return is_equal_guid(lhs, rhs); +} + +inline bool operator!=(const GUID & lhs, const GUID & rhs) +{ + return !(lhs == rhs); +} +#endif /* G_OS_WIN32 */ +#endif /* __cplusplus */ \ No newline at end of file diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264encoder.cpp b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264encoder.cpp new file mode 100644 index 0000000000..0574d6c07d --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264encoder.cpp @@ -0,0 +1,1928 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * 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 "gstnvh264encoder.h" +#include +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_STATIC (gst_nv_h264_encoder_debug); +#define GST_CAT_DEFAULT gst_nv_h264_encoder_debug + +static GTypeClass *parent_class = NULL; + +typedef struct +{ + gint max_bframes; + gint ratecontrol_modes; + gint field_encoding; + gint monochrome; + gint fmo; + gint qpelmv; + gint bdirect_mode; + gint cabac; + gint adaptive_transform; + gint stereo_mvc; + gint temoral_layers; + gint hierarchical_pframes; + gint hierarchical_bframes; + gint level_max; + gint level_min; + gint seperate_colour_plane; + gint width_max; + gint height_max; + gint temporal_svc; + gint dyn_res_change; + gint dyn_bitrate_change; + gint dyn_force_constqp; + gint dyn_rcmode_change; + gint subframe_readback; + gint constrained_encoding; + gint intra_refresh; + gint custom_vbv_buf_size; + gint dynamic_slice_mode; + gint ref_pic_invalidation; + gint preproc_support; + gint async_encoding_support; + gint mb_num_max; + gint mb_per_sec_max; + gint yuv444_encode; + gint lossless_encode; + gint meonly_mode; + gint lookahead; + gint temporal_aq; + gint supports_10bit_encode; + gint num_max_ltr_frames; + gint weighted_prediction; + gint bframe_ref_mode; + gint emphasis_level_map; + gint width_min; + gint height_min; + gint multiple_ref_frames; +} GstNvH264EncoderDeviceCaps; + +typedef struct +{ + GstCaps *sink_caps; + GstCaps *src_caps; + + guint cuda_device_id; + gint64 adapter_luid; + gboolean d3d11_mode; + + GstNvH264EncoderDeviceCaps dev_caps; +} GstNvH264EncoderClassData; + +enum +{ + PROP_0, + PROP_ADAPTER_LUID, + PROP_CUDA_DEVICE_ID, + + /* init params */ + PROP_PRESET, + PROP_WEIGHTED_PRED, + + /* encoding config */ + PROP_GOP_SIZE, + PROP_B_FRAMES, + + /* rate-control params */ + PROP_RC_MODE, + + PROP_QP_CONST_I, + PROP_QP_CONST_P, + PROP_QP_CONST_B, + + PROP_BITRATE, + PROP_MAX_BITRATE, + PROP_VBV_BUFFER_SIZE, + + PROP_RC_LOOKAHEAD, + PROP_I_ADAPT, + PROP_B_ADAPT, + PROP_SPATIAL_AQ, + PROP_TEMPORAL_AQ, + PROP_ZERO_LATENCY, + PROP_NON_REF_P, + PROP_STRICT_GOP, + PROP_AQ_STRENGTH, + + PROP_QP_MIN_I, + PROP_QP_MIN_P, + PROP_QP_MIN_B, + + PROP_QP_MAX_I, + PROP_QP_MAX_P, + PROP_QP_MAX_B, + + PROP_CONST_QUALITY, + + /* h264 specific */ + PROP_AUD, + PROP_CABAC, + PROP_REPEAT_SEQUENCE_HEADER, +}; + +#define DEFAULT_PRESET GST_NV_ENCODER_PRESET_DEFAULT +#define DEFAULT_WEIGHTED_PRED FALSE +#define DEFAULT_GOP_SIZE 75 +#define DEFAULT_B_FRAMES 0 +#define DEFAULT_RC_MODE GST_NV_ENCODER_RC_MODE_VBR +#define DEFAULT_QP -1 +#define DEFAULT_BITRATE 0 +#define DEFAULT_MAX_BITRATE 0 +#define DEFAULT_VBV_BUFFER_SIZE 0 +#define DEFAULT_RC_LOOKAHEAD 0 +#define DEFAULT_I_ADAPT FALSE +#define DEFAULT_B_ADAPT FALSE +#define DEFAULT_SPATIAL_AQ FALSE +#define DEFAULT_TEMPORAL_AQ FALSE +#define DEFAULT_ZERO_LATENCY FALSE +#define DEFAULT_NON_REF_P FALSE +#define DEFAULT_STRICT_GOP FALSE +#define DEFAULT_AQ_STRENGTH FALSE +#define DEFAULT_CONST_QUALITY 0 +#define DEFAULT_AUD TRUE +#define DEFAULT_REPEAT_SEQUENCE_HEADER FALSE + +typedef struct _GstNvH264Encoder +{ + GstNvEncoder parent; + GMutex prop_lock; + + gboolean init_param_updated; + gboolean rc_param_updated; + gboolean bitrate_updated; + + gboolean packetized; + GstH264NalParser *parser; + + /* Properties */ + GstNvEncoderPreset preset; + gboolean weighted_pred; + + gint gop_size; + guint bframes; + + GstNvEncoderRCMode rc_mode; + gint qp_const_i; + gint qp_const_p; + gint qp_const_b; + guint bitrate; + guint max_bitrate; + guint vbv_buffer_size; + guint rc_lookahead; + gboolean i_adapt; + gboolean b_adapt; + gboolean spatial_aq; + gboolean temporal_aq; + gboolean zero_latency; + gboolean non_ref_p; + gboolean strict_gop; + guint aq_strength; + gint qp_min_i; + gint qp_min_p; + gint qp_min_b; + gint qp_max_i; + gint qp_max_p; + gint qp_max_b; + gdouble const_quality; + + gboolean aud; + gboolean cabac; + gboolean repeat_sequence_header; +} GstNvH264Encoder; + +typedef struct _GstNvH264EncoderClass +{ + GstNvEncoderClass parent_class; + GstNvH264EncoderDeviceCaps dev_caps; + + guint cuda_device_id; + gint64 adapter_luid; + gboolean d3d11_mode; +} GstNvH264EncoderClass; + +#define GST_NV_H264_ENCODER(object) ((GstNvH264Encoder *) (object)) +#define GST_NV_H264_ENCODER_GET_CLASS(object) \ + (G_TYPE_INSTANCE_GET_CLASS ((object),G_TYPE_FROM_INSTANCE (object),GstNvH264EncoderClass)) + +static void gst_nv_h264_encoder_finalize (GObject * object); +static void gst_nv_h264_encoder_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_nv_h264_encoder_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static GstCaps *gst_nv_h264_encoder_getcaps (GstVideoEncoder * encoder, + GstCaps * filter); +static gboolean gst_nv_h264_encoder_set_format (GstNvEncoder * encoder, + GstVideoCodecState * state, gpointer session, + NV_ENC_INITIALIZE_PARAMS * init_params, NV_ENC_CONFIG * config); +static gboolean gst_nv_h264_encoder_set_output_state (GstNvEncoder * encoder, + GstVideoCodecState * state, gpointer session); +static GstBuffer *gst_nv_h264_encoder_create_output_buffer (GstNvEncoder * + encoder, NV_ENC_LOCK_BITSTREAM * bitstream); +static GstNvEncoderReconfigure +gst_nv_h264_encoder_check_reconfigure (GstNvEncoder * encoder, + NV_ENC_CONFIG * config); + +static void +gst_nv_h264_encoder_class_init (GstNvH264EncoderClass * klass, gpointer data) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstVideoEncoderClass *videoenc_class = GST_VIDEO_ENCODER_CLASS (klass); + GstNvEncoderClass *nvenc_class = GST_NV_ENCODER_CLASS (klass); + GstNvH264EncoderClassData *cdata = (GstNvH264EncoderClassData *) data; + GstNvH264EncoderDeviceCaps *dev_caps = &cdata->dev_caps; + GParamFlags param_flags = (GParamFlags) (G_PARAM_READWRITE | + GST_PARAM_MUTABLE_PLAYING | G_PARAM_STATIC_STRINGS); + GParamFlags conditonal_param_flags = (GParamFlags) (G_PARAM_READWRITE | + GST_PARAM_CONDITIONALLY_AVAILABLE | GST_PARAM_MUTABLE_PLAYING | + G_PARAM_STATIC_STRINGS); + + parent_class = (GTypeClass *) g_type_class_peek_parent (klass); + + object_class->finalize = gst_nv_h264_encoder_finalize; + object_class->set_property = gst_nv_h264_encoder_set_property; + object_class->get_property = gst_nv_h264_encoder_get_property; + + if (cdata->d3d11_mode) { + g_object_class_install_property (object_class, PROP_ADAPTER_LUID, + g_param_spec_int64 ("adapter-luid", "Adapter LUID", + "DXGI Adapter LUID (Locally Unique Identifier) of associated GPU", + G_MININT64, G_MAXINT64, cdata->adapter_luid, + (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + } else { + g_object_class_install_property (object_class, PROP_CUDA_DEVICE_ID, + g_param_spec_uint ("cuda-device-id", "CUDA Device ID", + "CUDA device ID of associated GPU", + 0, G_MAXINT, cdata->cuda_device_id, + (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + } + g_object_class_install_property (object_class, PROP_PRESET, + g_param_spec_enum ("preset", "Encoding Preset", + "Encoding Preset", GST_TYPE_NV_ENCODER_PRESET, + DEFAULT_PRESET, param_flags)); + if (dev_caps->weighted_prediction) { + g_object_class_install_property (object_class, PROP_WEIGHTED_PRED, + g_param_spec_boolean ("weighted-pred", "Weighted Pred", + "Enables Weighted Prediction", DEFAULT_WEIGHTED_PRED, + conditonal_param_flags)); + } + g_object_class_install_property (object_class, PROP_GOP_SIZE, + g_param_spec_int ("gop-size", "GOP size", + "Number of frames between intra frames (-1 = infinite)", + -1, G_MAXINT, DEFAULT_GOP_SIZE, param_flags)); + if (dev_caps->max_bframes > 0) { + g_object_class_install_property (object_class, PROP_B_FRAMES, + g_param_spec_uint ("bframes", "B-Frames", + "Number of B-frames between I and P", 0, dev_caps->max_bframes, + DEFAULT_B_FRAMES, conditonal_param_flags)); + } + g_object_class_install_property (object_class, PROP_RC_MODE, + g_param_spec_enum ("rc-mode", "RC Mode", "Rate Control Mode", + GST_TYPE_NV_ENCODER_RC_MODE, DEFAULT_RC_MODE, param_flags)); + g_object_class_install_property (object_class, PROP_QP_CONST_I, + g_param_spec_int ("qp-const-i", "QP Const I", + "Constant QP value for I frame (-1 = disabled)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_CONST_P, + g_param_spec_int ("qp-const-p", "QP Const P", + "Constant QP value for P frame (-1 = disabled)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_CONST_B, + g_param_spec_int ("qp-const-b", "QP Const B", + "Constant QP value for B frame (-1 = disabled)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_BITRATE, + g_param_spec_uint ("bitrate", "Bitrate", + "Bitrate in kbit/sec (0 = automatic)", 0, 2000 * 1024, + DEFAULT_BITRATE, param_flags)); + g_object_class_install_property (object_class, PROP_MAX_BITRATE, + g_param_spec_uint ("max-bitrate", "Max Bitrate", + "Maximum Bitrate in kbit/sec (ignored in CBR mode)", 0, 2000 * 1024, + DEFAULT_MAX_BITRATE, param_flags)); + if (dev_caps->custom_vbv_buf_size) { + g_object_class_install_property (object_class, + PROP_VBV_BUFFER_SIZE, + g_param_spec_uint ("vbv-buffer-size", "VBV Buffer Size", + "VBV(HRD) Buffer Size in kbits (0 = NVENC default)", + 0, G_MAXUINT, DEFAULT_VBV_BUFFER_SIZE, conditonal_param_flags)); + } + if (dev_caps->lookahead) { + g_object_class_install_property (object_class, PROP_RC_LOOKAHEAD, + g_param_spec_uint ("rc-lookahead", "Rate Control Lookahead", + "Number of frames for frame type lookahead", + 0, 32, DEFAULT_RC_LOOKAHEAD, conditonal_param_flags)); + g_object_class_install_property (object_class, PROP_I_ADAPT, + g_param_spec_boolean ("i-adapt", "I Adapt", + "Enable adaptive I-frame insert when lookahead is enabled", + DEFAULT_I_ADAPT, conditonal_param_flags)); + if (dev_caps->max_bframes > 0) { + g_object_class_install_property (object_class, PROP_B_ADAPT, + g_param_spec_boolean ("b-adapt", "B Adapt", + "Enable adaptive B-frame insert when lookahead is enabled", + DEFAULT_B_ADAPT, conditonal_param_flags)); + } + } + g_object_class_install_property (object_class, PROP_SPATIAL_AQ, + g_param_spec_boolean ("spatial-aq", "Spatial AQ", + "Spatial Adaptive Quantization", DEFAULT_SPATIAL_AQ, param_flags)); + if (dev_caps->temporal_aq) { + g_object_class_install_property (object_class, PROP_TEMPORAL_AQ, + g_param_spec_boolean ("temporal-aq", "Temporal AQ", + "Temporal Adaptive Quantization", DEFAULT_TEMPORAL_AQ, + conditonal_param_flags)); + } + g_object_class_install_property (object_class, PROP_ZERO_LATENCY, + g_param_spec_boolean ("zerolatency", "Zerolatency", + "Zero latency operation (no reordering delay)", DEFAULT_ZERO_LATENCY, + param_flags)); + g_object_class_install_property (object_class, PROP_NON_REF_P, + g_param_spec_boolean ("nonref-p", "Nonref P", + "Automatic insertion of non-reference P-frames", DEFAULT_NON_REF_P, + param_flags)); + g_object_class_install_property (object_class, PROP_STRICT_GOP, + g_param_spec_boolean ("strict-gop", "Strict GOP", + "Minimize GOP-to-GOP rate fluctuations", DEFAULT_STRICT_GOP, + param_flags)); + g_object_class_install_property (object_class, PROP_AQ_STRENGTH, + g_param_spec_uint ("aq-strength", "AQ Strength", + "Adaptive Quantization Strength when spatial-aq is enabled" + " from 1 (low) to 15 (aggressive), (0 = autoselect)", + 0, 15, DEFAULT_AQ_STRENGTH, param_flags)); + g_object_class_install_property (object_class, PROP_QP_MIN_I, + g_param_spec_int ("qp-min-i", "QP Min I", + "Minimum QP value for I frame, (-1 = disabled)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_MIN_P, + g_param_spec_int ("qp-min-p", "QP Min P", + "Minimum QP value for P frame, (-1 = automatic)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_MIN_B, + g_param_spec_int ("qp-min-b", "QP Min B", + "Minimum QP value for B frame, (-1 = automatic)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_MAX_I, + g_param_spec_int ("qp-max-i", "QP Max I", + "Maximum QP value for I frame, (-1 = disabled)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_MAX_P, + g_param_spec_int ("qp-max-p", "QP Max P", + "Maximum QP value for P frame, (-1 = automatic)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_MAX_B, + g_param_spec_int ("qp-max-b", "QP Max B", + "Maximum QP value for B frame, (-1 = automatic)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_CONST_QUALITY, + g_param_spec_double ("const-quality", "Constant Quality", + "Target Constant Quality level for VBR mode (0 = automatic)", + 0, 51, DEFAULT_CONST_QUALITY, param_flags)); + g_object_class_install_property (object_class, PROP_AUD, + g_param_spec_boolean ("aud", "AUD", + "Use AU (Access Unit) delimiter", DEFAULT_AUD, param_flags)); + if (dev_caps->cabac) { + g_object_class_install_property (object_class, PROP_CABAC, + g_param_spec_boolean ("cabac", "CABAC", + "Enable CABAC entropy coding", TRUE, conditonal_param_flags)); + } + g_object_class_install_property (object_class, PROP_REPEAT_SEQUENCE_HEADER, + g_param_spec_boolean ("repeat-sequence-header", "Repeat Sequence Header", + "Insert sequence headers (SPS/PPS) per IDR", + DEFAULT_REPEAT_SEQUENCE_HEADER, param_flags)); + + if (cdata->d3d11_mode) { + gst_element_class_set_metadata (element_class, + "NVENC H.264 Video Encoder Direct3D11 Mode", + "Codec/Encoder/Video/Hardware", + "Encode H.264 video streams using NVCODEC API Direct3D11 Mode", + "Seungha Yang "); + } else { + gst_element_class_set_metadata (element_class, + "NVENC H.264 Video Encoder CUDA Mode", + "Codec/Encoder/Video/Hardware", + "Encode H.264 video streams using NVCODEC API CUDA Mode", + "Seungha Yang "); + } + + gst_element_class_add_pad_template (element_class, + gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + cdata->sink_caps)); + gst_element_class_add_pad_template (element_class, + gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + cdata->src_caps)); + + videoenc_class->getcaps = GST_DEBUG_FUNCPTR (gst_nv_h264_encoder_getcaps); + + nvenc_class->set_format = GST_DEBUG_FUNCPTR (gst_nv_h264_encoder_set_format); + nvenc_class->set_output_state = + GST_DEBUG_FUNCPTR (gst_nv_h264_encoder_set_output_state); + nvenc_class->create_output_buffer = + GST_DEBUG_FUNCPTR (gst_nv_h264_encoder_create_output_buffer); + nvenc_class->check_reconfigure = + GST_DEBUG_FUNCPTR (gst_nv_h264_encoder_check_reconfigure); + + klass->dev_caps = cdata->dev_caps; + klass->cuda_device_id = cdata->cuda_device_id; + klass->adapter_luid = cdata->adapter_luid; + klass->d3d11_mode = cdata->d3d11_mode; + + gst_caps_unref (cdata->sink_caps); + gst_caps_unref (cdata->src_caps); + g_free (cdata); +} + +static void +gst_nv_h264_encoder_init (GstNvH264Encoder * self) +{ + GstNvH264EncoderClass *klass = GST_NV_H264_ENCODER_GET_CLASS (self); + + g_mutex_init (&self->prop_lock); + + self->preset = DEFAULT_PRESET; + self->weighted_pred = DEFAULT_WEIGHTED_PRED; + self->gop_size = DEFAULT_GOP_SIZE; + self->bframes = DEFAULT_B_FRAMES; + self->rc_mode = DEFAULT_RC_MODE; + self->qp_const_i = DEFAULT_QP; + self->qp_const_p = DEFAULT_QP; + self->qp_const_b = DEFAULT_QP; + self->bitrate = DEFAULT_BITRATE; + self->max_bitrate = DEFAULT_MAX_BITRATE; + self->vbv_buffer_size = DEFAULT_VBV_BUFFER_SIZE; + self->rc_lookahead = DEFAULT_RC_LOOKAHEAD; + self->i_adapt = DEFAULT_I_ADAPT; + self->b_adapt = DEFAULT_B_ADAPT; + self->spatial_aq = DEFAULT_SPATIAL_AQ; + self->temporal_aq = DEFAULT_TEMPORAL_AQ; + self->zero_latency = DEFAULT_ZERO_LATENCY; + self->non_ref_p = DEFAULT_NON_REF_P; + self->strict_gop = DEFAULT_STRICT_GOP; + self->aq_strength = DEFAULT_AQ_STRENGTH; + self->qp_min_i = DEFAULT_QP; + self->qp_min_p = DEFAULT_QP; + self->qp_min_b = DEFAULT_QP; + self->qp_max_i = DEFAULT_QP; + self->qp_max_p = DEFAULT_QP; + self->qp_max_b = DEFAULT_QP; + self->const_quality = DEFAULT_CONST_QUALITY; + self->aud = DEFAULT_AUD; + if (klass->dev_caps.cabac) + self->cabac = TRUE; + self->repeat_sequence_header = DEFAULT_REPEAT_SEQUENCE_HEADER; + + self->parser = gst_h264_nal_parser_new (); + + if (klass->d3d11_mode) { + gst_nv_encoder_set_dxgi_adapter_luid (GST_NV_ENCODER (self), + klass->adapter_luid); + } else { + gst_nv_encoder_set_cuda_device_id (GST_NV_ENCODER (self), + klass->cuda_device_id); + } +} + +static void +gst_nv_h264_encoder_finalize (GObject * object) +{ + GstNvH264Encoder *self = GST_NV_H264_ENCODER (object); + + g_mutex_clear (&self->prop_lock); + gst_h264_nal_parser_free (self->parser); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +typedef enum +{ + UPDATE_INIT_PARAM, + UPDATE_RC_PARAM, + UPDATE_BITRATE, +} PropUpdateLevel; + +static void +update_boolean (GstNvH264Encoder * self, gboolean * old_val, + const GValue * new_val, PropUpdateLevel level) +{ + gboolean val = g_value_get_boolean (new_val); + + if (*old_val == val) + return; + + *old_val = val; + switch (level) { + case UPDATE_INIT_PARAM: + self->init_param_updated = TRUE; + break; + case UPDATE_RC_PARAM: + self->rc_param_updated = TRUE; + break; + case UPDATE_BITRATE: + self->bitrate_updated = TRUE; + break; + } +} + +static void +update_int (GstNvH264Encoder * self, gint * old_val, + const GValue * new_val, PropUpdateLevel level) +{ + gint val = g_value_get_int (new_val); + + if (*old_val == val) + return; + + *old_val = val; + switch (level) { + case UPDATE_INIT_PARAM: + self->init_param_updated = TRUE; + break; + case UPDATE_RC_PARAM: + self->rc_param_updated = TRUE; + break; + case UPDATE_BITRATE: + self->bitrate_updated = TRUE; + break; + } +} + +static void +update_uint (GstNvH264Encoder * self, guint * old_val, + const GValue * new_val, PropUpdateLevel level) +{ + guint val = g_value_get_uint (new_val); + + if (*old_val == val) + return; + + *old_val = val; + switch (level) { + case UPDATE_INIT_PARAM: + self->init_param_updated = TRUE; + break; + case UPDATE_RC_PARAM: + self->rc_param_updated = TRUE; + break; + case UPDATE_BITRATE: + self->bitrate_updated = TRUE; + break; + } +} + +static void +update_double (GstNvH264Encoder * self, gdouble * old_val, + const GValue * new_val, PropUpdateLevel level) +{ + gdouble val = g_value_get_double (new_val); + + if (*old_val == val) + return; + + *old_val = val; + switch (level) { + case UPDATE_INIT_PARAM: + self->init_param_updated = TRUE; + break; + case UPDATE_RC_PARAM: + self->rc_param_updated = TRUE; + break; + case UPDATE_BITRATE: + self->bitrate_updated = TRUE; + break; + } +} + +static void +gst_nv_h264_encoder_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstNvH264Encoder *self = GST_NV_H264_ENCODER (object); + + g_mutex_lock (&self->prop_lock); + switch (prop_id) { + case PROP_PRESET:{ + GstNvEncoderPreset preset = (GstNvEncoderPreset) g_value_get_enum (value); + if (preset != self->preset) { + self->preset = preset; + self->init_param_updated = TRUE; + } + break; + } + case PROP_WEIGHTED_PRED: + update_boolean (self, &self->weighted_pred, value, UPDATE_INIT_PARAM); + break; + case PROP_GOP_SIZE: + update_int (self, &self->gop_size, value, UPDATE_INIT_PARAM); + break; + case PROP_B_FRAMES: + update_uint (self, &self->bframes, value, UPDATE_INIT_PARAM); + break; + case PROP_RC_MODE:{ + GstNvEncoderRCMode mode = (GstNvEncoderRCMode) g_value_get_enum (value); + if (mode != self->rc_mode) { + self->rc_mode = mode; + self->rc_param_updated = TRUE; + } + break; + } + case PROP_QP_CONST_I: + update_int (self, &self->qp_const_i, value, UPDATE_RC_PARAM); + break; + case PROP_QP_CONST_P: + update_int (self, &self->qp_const_p, value, UPDATE_RC_PARAM); + break; + case PROP_QP_CONST_B: + update_int (self, &self->qp_const_b, value, UPDATE_RC_PARAM); + break; + case PROP_BITRATE: + update_uint (self, &self->bitrate, value, UPDATE_BITRATE); + break; + case PROP_MAX_BITRATE: + update_uint (self, &self->max_bitrate, value, UPDATE_BITRATE); + break; + case PROP_VBV_BUFFER_SIZE: + update_uint (self, &self->vbv_buffer_size, value, UPDATE_RC_PARAM); + break; + case PROP_RC_LOOKAHEAD: + /* rc-lookahead update requires pool size change */ + update_uint (self, &self->rc_lookahead, value, UPDATE_INIT_PARAM); + break; + case PROP_I_ADAPT: + update_boolean (self, &self->i_adapt, value, UPDATE_RC_PARAM); + break; + case PROP_B_ADAPT: + update_boolean (self, &self->b_adapt, value, UPDATE_RC_PARAM); + break; + case PROP_SPATIAL_AQ: + update_boolean (self, &self->spatial_aq, value, UPDATE_RC_PARAM); + break; + case PROP_TEMPORAL_AQ: + update_boolean (self, &self->temporal_aq, value, UPDATE_RC_PARAM); + break; + case PROP_ZERO_LATENCY: + update_boolean (self, &self->zero_latency, value, UPDATE_RC_PARAM); + break; + case PROP_NON_REF_P: + update_boolean (self, &self->non_ref_p, value, UPDATE_RC_PARAM); + break; + case PROP_STRICT_GOP: + update_boolean (self, &self->strict_gop, value, UPDATE_RC_PARAM); + break; + case PROP_AQ_STRENGTH: + update_uint (self, &self->aq_strength, value, UPDATE_RC_PARAM); + break; + case PROP_QP_MIN_I: + update_int (self, &self->qp_min_i, value, UPDATE_RC_PARAM); + break; + case PROP_QP_MIN_P: + update_int (self, &self->qp_min_p, value, UPDATE_RC_PARAM); + break; + case PROP_QP_MIN_B: + update_int (self, &self->qp_min_b, value, UPDATE_RC_PARAM); + break; + case PROP_QP_MAX_I: + update_int (self, &self->qp_min_i, value, UPDATE_RC_PARAM); + break; + case PROP_QP_MAX_P: + update_int (self, &self->qp_min_p, value, UPDATE_RC_PARAM); + break; + case PROP_QP_MAX_B: + update_int (self, &self->qp_min_b, value, UPDATE_RC_PARAM); + break; + case PROP_CONST_QUALITY: + update_double (self, &self->const_quality, value, UPDATE_RC_PARAM); + break; + case PROP_AUD: + update_boolean (self, &self->aud, value, UPDATE_INIT_PARAM); + break; + case PROP_CABAC: + update_boolean (self, &self->cabac, value, UPDATE_INIT_PARAM); + break; + case PROP_REPEAT_SEQUENCE_HEADER: + update_boolean (self, + &self->repeat_sequence_header, value, UPDATE_INIT_PARAM); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + g_mutex_unlock (&self->prop_lock); +} + +static void +gst_nv_h264_encoder_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstNvH264Encoder *self = GST_NV_H264_ENCODER (object); + GstNvH264EncoderClass *klass = GST_NV_H264_ENCODER_GET_CLASS (self); + + switch (prop_id) { + case PROP_ADAPTER_LUID: + g_value_set_int64 (value, klass->adapter_luid); + break; + case PROP_CUDA_DEVICE_ID: + g_value_set_uint (value, klass->cuda_device_id); + break; + case PROP_PRESET: + g_value_set_enum (value, self->preset); + break; + case PROP_WEIGHTED_PRED: + g_value_set_boolean (value, self->weighted_pred); + break; + case PROP_GOP_SIZE: + g_value_set_int (value, self->gop_size); + break; + case PROP_B_FRAMES: + g_value_set_uint (value, self->bframes); + break; + case PROP_RC_MODE: + g_value_set_enum (value, self->rc_mode); + break; + case PROP_QP_CONST_I: + g_value_set_int (value, self->qp_const_i); + break; + case PROP_QP_CONST_P: + g_value_set_int (value, self->qp_const_p); + break; + case PROP_QP_CONST_B: + g_value_set_int (value, self->qp_const_b); + break; + case PROP_BITRATE: + g_value_set_uint (value, self->bitrate); + break; + case PROP_MAX_BITRATE: + g_value_set_uint (value, self->max_bitrate); + break; + case PROP_VBV_BUFFER_SIZE: + g_value_set_uint (value, self->vbv_buffer_size); + break; + case PROP_RC_LOOKAHEAD: + g_value_set_uint (value, self->rc_lookahead); + break; + case PROP_I_ADAPT: + g_value_set_boolean (value, self->i_adapt); + break; + case PROP_B_ADAPT: + g_value_set_boolean (value, self->b_adapt); + break; + case PROP_SPATIAL_AQ: + g_value_set_boolean (value, self->spatial_aq); + break; + case PROP_TEMPORAL_AQ: + g_value_set_boolean (value, self->temporal_aq); + break; + case PROP_ZERO_LATENCY: + g_value_set_boolean (value, self->zero_latency); + break; + case PROP_NON_REF_P: + g_value_set_boolean (value, self->non_ref_p); + break; + case PROP_STRICT_GOP: + g_value_set_boolean (value, self->strict_gop); + break; + case PROP_AQ_STRENGTH: + g_value_set_uint (value, self->aq_strength); + break; + case PROP_QP_MIN_I: + g_value_set_int (value, self->qp_min_i); + break; + case PROP_QP_MIN_P: + g_value_set_int (value, self->qp_min_p); + break; + case PROP_QP_MIN_B: + g_value_set_int (value, self->qp_min_b); + break; + case PROP_QP_MAX_I: + g_value_set_int (value, self->qp_max_i); + break; + case PROP_QP_MAX_P: + g_value_set_int (value, self->qp_max_p); + break; + case PROP_QP_MAX_B: + g_value_set_int (value, self->qp_max_b); + break; + case PROP_CONST_QUALITY: + g_value_set_double (value, self->const_quality); + break; + case PROP_AUD: + g_value_set_boolean (value, self->aud); + break; + case PROP_CABAC: + g_value_set_boolean (value, self->cabac); + break; + case PROP_REPEAT_SEQUENCE_HEADER: + g_value_set_boolean (value, self->repeat_sequence_header); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_nv_h264_encoder_get_downstream_profiles_and_format (GstNvH264Encoder * self, + std::set < std::string > &downstream_profiles, gboolean * packetized) +{ + GstCaps *allowed_caps; + GstStructure *s; + const gchar *stream_format; + + allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (self)); + + if (!allowed_caps || gst_caps_is_empty (allowed_caps) || + gst_caps_is_any (allowed_caps)) { + gst_clear_caps (&allowed_caps); + + return; + } + + for (guint i = 0; i < gst_caps_get_size (allowed_caps); i++) { + const GValue *profile_value; + const gchar *profile; + + s = gst_caps_get_structure (allowed_caps, i); + profile_value = gst_structure_get_value (s, "profile"); + if (!profile_value) + continue; + + if (GST_VALUE_HOLDS_LIST (profile_value)) { + for (guint j = 0; j < gst_value_list_get_size (profile_value); j++) { + const GValue *p = gst_value_list_get_value (profile_value, j); + + if (!G_VALUE_HOLDS_STRING (p)) + continue; + + profile = g_value_get_string (p); + if (profile) + downstream_profiles.insert (profile); + } + + } else if (G_VALUE_HOLDS_STRING (profile_value)) { + profile = g_value_get_string (profile_value); + if (profile) + downstream_profiles.insert (profile); + } + } + + if (packetized) { + *packetized = FALSE; + allowed_caps = gst_caps_fixate (allowed_caps); + s = gst_caps_get_structure (allowed_caps, 0); + stream_format = gst_structure_get_string (s, "stream-format"); + if (g_strcmp0 (stream_format, "avc") == 0) + *packetized = TRUE; + } + + gst_caps_unref (allowed_caps); +} + +static GstCaps * +gst_nv_h264_encoder_getcaps (GstVideoEncoder * encoder, GstCaps * filter) +{ + GstNvH264Encoder *self = GST_NV_H264_ENCODER (encoder); + GstNvH264EncoderClass *klass = GST_NV_H264_ENCODER_GET_CLASS (self); + GstCaps *allowed_caps; + GstCaps *template_caps; + GstCaps *filtered_caps; + GstCaps *supported_caps; + std::set < std::string > downstream_profiles; + std::set < std::string > allowed_formats; + gboolean profile_support_interlaced = FALSE; + + gst_nv_h264_encoder_get_downstream_profiles_and_format (self, + downstream_profiles, NULL); + + GST_DEBUG_OBJECT (self, "Downstream specified %" G_GSIZE_FORMAT " profiles", + downstream_profiles.size ()); + + if (downstream_profiles.size () == 0) + return gst_video_encoder_proxy_getcaps (encoder, NULL, filter); + + /* *INDENT-OFF* */ + for (const auto &iter: downstream_profiles) { + if (iter == "high" || iter == "main") { + profile_support_interlaced = TRUE; + } + + if (iter == "high-4:4:4") { + profile_support_interlaced = TRUE; + allowed_formats.insert("Y444"); + } else { + allowed_formats.insert("NV12"); + } + } + /* *INDENT-ON* */ + + GST_DEBUG_OBJECT (self, "Downstream %s support interlaced format", + profile_support_interlaced ? "can" : "cannot"); + + template_caps = gst_pad_get_pad_template_caps (encoder->sinkpad); + allowed_caps = gst_caps_copy (template_caps); + + if (klass->dev_caps.field_encoding == 0 || !profile_support_interlaced) { + gst_caps_set_simple (allowed_caps, "interlace-mode", G_TYPE_STRING, + "progressive", NULL); + } + + GValue formats = G_VALUE_INIT; + + g_value_init (&formats, GST_TYPE_LIST); + /* *INDENT-OFF* */ + for (const auto &iter: allowed_formats) { + GValue val = G_VALUE_INIT; + g_value_init (&val, G_TYPE_STRING); + + g_value_set_string (&val, iter.c_str()); + gst_value_list_append_and_take_value (&formats, &val); + } + /* *INDENT-ON* */ + + gst_caps_set_value (allowed_caps, "format", &formats); + g_value_unset (&formats); + + filtered_caps = gst_caps_intersect_full (template_caps, allowed_caps, + GST_CAPS_INTERSECT_FIRST); + + supported_caps = gst_video_encoder_proxy_getcaps (encoder, + filtered_caps, filter); + gst_caps_unref (filtered_caps); + gst_caps_unref (allowed_caps); + gst_caps_unref (template_caps); + + GST_DEBUG_OBJECT (self, "Returning %" GST_PTR_FORMAT, supported_caps); + + return supported_caps; +} + +static gboolean +gst_nv_h264_encoder_set_format (GstNvEncoder * encoder, + GstVideoCodecState * state, gpointer session, + NV_ENC_INITIALIZE_PARAMS * init_params, NV_ENC_CONFIG * config) +{ + GstNvH264Encoder *self = GST_NV_H264_ENCODER (encoder); + GstNvH264EncoderClass *klass = GST_NV_H264_ENCODER_GET_CLASS (self); + GstNvH264EncoderDeviceCaps *dev_caps = &klass->dev_caps; + NV_ENC_RC_PARAMS *rc_params; + GstVideoInfo *info = &state->info; + NVENCSTATUS status; + NV_ENC_PRESET_CONFIG preset_config = { 0, }; + gint dar_n, dar_d; + GstNvEncoderRCMode rc_mode; + NV_ENC_CONFIG_H264 *h264_config; + NV_ENC_CONFIG_H264_VUI_PARAMETERS *vui; + std::set < std::string > downstream_profiles; + GUID selected_profile = NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID; + gboolean downstream_supports_bframe = FALSE; + gboolean bframe_aborted = FALSE; + + self->packetized = FALSE; + + gst_nv_h264_encoder_get_downstream_profiles_and_format (self, + downstream_profiles, &self->packetized); + + if (downstream_profiles.empty ()) { + GST_ERROR_OBJECT (self, "Unable to get downstream profile"); + return FALSE; + } + + if (GST_VIDEO_INFO_IS_INTERLACED (info)) { + downstream_profiles.erase ("progressive-high"); + downstream_profiles.erase ("constrained-high"); + downstream_profiles.erase ("constrained-baseline"); + downstream_profiles.erase ("baseline"); + + if (downstream_profiles.empty ()) { + GST_ERROR_OBJECT (self, + "None of downstream profile supports interlaced encoding"); + return FALSE; + } + } + + if (GST_VIDEO_INFO_FORMAT (info) == GST_VIDEO_FORMAT_Y444) { + if (downstream_profiles.find ("high-4:4:4") == downstream_profiles.end ()) { + GST_ERROR_OBJECT (self, "Downstream does not support 4:4:4 profile"); + return FALSE; + } else { + selected_profile = NV_ENC_H264_PROFILE_HIGH_444_GUID; + downstream_supports_bframe = TRUE; + } + } else { + /* *INDENT-OFF* */ + for (const auto &iter: downstream_profiles) { + if (iter == "high" || iter == "main" || iter == "progressive-high") { + downstream_supports_bframe = TRUE; + } + } + /* *INDENT-ON* */ + } + + g_mutex_lock (&self->prop_lock); + + init_params->version = gst_nvenc_get_initialize_params_version (); + init_params->encodeGUID = NV_ENC_CODEC_H264_GUID; + + init_params->encodeWidth = GST_VIDEO_INFO_WIDTH (info); + init_params->maxEncodeWidth = GST_VIDEO_INFO_WIDTH (info); + init_params->encodeHeight = GST_VIDEO_INFO_HEIGHT (info); + init_params->maxEncodeHeight = GST_VIDEO_INFO_HEIGHT (info); + init_params->enablePTD = TRUE; + if (dev_caps->async_encoding_support) + init_params->enableEncodeAsync = 1; + if (info->fps_d > 0 && info->fps_n > 0) { + init_params->frameRateNum = info->fps_n; + init_params->frameRateDen = info->fps_d; + } else { + init_params->frameRateNum = 0; + init_params->frameRateDen = 1; + } + + init_params->enableWeightedPrediction = self->weighted_pred; + + if (gst_util_fraction_multiply (GST_VIDEO_INFO_WIDTH (info), + GST_VIDEO_INFO_HEIGHT (info), GST_VIDEO_INFO_PAR_N (info), + GST_VIDEO_INFO_PAR_D (info), &dar_n, &dar_d) && dar_n > 0 + && dar_d > 0) { + init_params->darWidth = dar_n; + init_params->darHeight = dar_d; + } + + if (GST_VIDEO_INFO_IS_INTERLACED (info) && dev_caps->field_encoding > 0) { + switch (GST_VIDEO_INFO_INTERLACE_MODE (info)) { + case GST_VIDEO_INTERLACE_MODE_INTERLEAVED: + case GST_VIDEO_INTERLACE_MODE_MIXED: + config->frameFieldMode = NV_ENC_PARAMS_FRAME_FIELD_MODE_FIELD; + preset_config.presetCfg.frameFieldMode = + NV_ENC_PARAMS_FRAME_FIELD_MODE_FIELD; + break; + default: + break; + } + } + + gst_nv_encoder_preset_to_guid (self->preset, &init_params->presetGUID); + + preset_config.version = gst_nvenc_get_preset_config_version (); + preset_config.presetCfg.version = gst_nvenc_get_config_version (); + + status = NvEncGetEncodePresetConfig (session, NV_ENC_CODEC_H264_GUID, + init_params->presetGUID, &preset_config); + if (status != NV_ENC_SUCCESS) { + GST_ERROR_OBJECT (self, "Failed to get preset config %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + g_mutex_unlock (&self->prop_lock); + return FALSE; + } + + *config = preset_config.presetCfg; + if (self->gop_size < 0) { + config->gopLength = NVENC_INFINITE_GOPLENGTH; + config->frameIntervalP = 1; + } else if (self->gop_size > 0) { + config->gopLength = self->gop_size; + /* frameIntervalP + * 0: All Intra frames + * 1: I/P only + * 2: IBP + * 3: IBBP + */ + config->frameIntervalP = 1; + if (self->bframes > 0 && !downstream_supports_bframe) { + GST_WARNING_OBJECT (self, + "B-frame was enabled but downstream profile does not support it"); + bframe_aborted = TRUE; + self->bframes = 0; + } + + config->frameIntervalP = self->bframes + 1; + } else { + /* gop size == 0 means all intra frames */ + config->gopLength = 1; + config->frameIntervalP = 0; + } + + rc_params = &config->rcParams; + rc_mode = self->rc_mode; + + if (self->bitrate) + rc_params->averageBitRate = self->bitrate * 1024; + if (self->max_bitrate) + rc_params->maxBitRate = self->max_bitrate * 1024; + if (self->vbv_buffer_size) + rc_params->vbvBufferSize = self->vbv_buffer_size * 1024; + + if (rc_mode == GST_NV_ENCODER_RC_MODE_DEFAULT) { + if (self->qp_const_i >= 0) + rc_mode = GST_NV_ENCODER_RC_MODE_CONSTQP; + } + + if (self->qp_min_i >= 0) { + rc_params->enableMinQP = TRUE; + rc_params->minQP.qpIntra = self->qp_min_i; + if (self->qp_min_p >= 0) { + rc_params->minQP.qpInterP = self->qp_min_p; + } else { + rc_params->minQP.qpInterP = rc_params->minQP.qpIntra; + } + if (self->qp_min_b >= 0) { + rc_params->minQP.qpInterB = self->qp_min_b; + } else { + rc_params->minQP.qpInterB = rc_params->minQP.qpInterP; + } + } + + if (self->qp_max_i >= 0) { + rc_params->enableMaxQP = TRUE; + rc_params->maxQP.qpIntra = self->qp_max_i; + if (self->qp_max_p >= 0) { + rc_params->maxQP.qpInterP = self->qp_max_p; + } else { + rc_params->maxQP.qpInterP = rc_params->maxQP.qpIntra; + } + if (self->qp_max_b >= 0) { + rc_params->maxQP.qpInterB = self->qp_max_b; + } else { + rc_params->maxQP.qpInterB = rc_params->maxQP.qpInterP; + } + } + + if (rc_mode == GST_NV_ENCODER_RC_MODE_CONSTQP && self->qp_const_i >= 0) { + rc_params->enableMaxQP = TRUE; + rc_params->maxQP.qpIntra = self->qp_max_i; + if (self->qp_max_p >= 0) { + rc_params->maxQP.qpInterP = self->qp_max_p; + } else { + rc_params->maxQP.qpInterP = rc_params->maxQP.qpIntra; + } + if (self->qp_max_b >= 0) { + rc_params->maxQP.qpInterB = self->qp_max_b; + } else { + rc_params->maxQP.qpInterB = rc_params->maxQP.qpInterP; + } + } + + rc_params->rateControlMode = gst_nv_encoder_rc_mode_to_native (rc_mode); + + if (self->spatial_aq) { + rc_params->enableAQ = TRUE; + rc_params->aqStrength = self->aq_strength; + } + + rc_params->enableTemporalAQ = self->temporal_aq; + + if (self->rc_lookahead) { + rc_params->enableLookahead = 1; + rc_params->lookaheadDepth = self->rc_lookahead; + rc_params->disableIadapt = !self->i_adapt; + rc_params->disableBadapt = !self->b_adapt; + } + + rc_params->strictGOPTarget = self->strict_gop; + rc_params->enableNonRefP = self->non_ref_p; + rc_params->zeroReorderDelay = self->zero_latency; + + if (self->const_quality) { + guint scaled = (gint) (self->const_quality * 256.0); + + rc_params->targetQuality = (guint8) (scaled >> 8); + rc_params->targetQualityLSB = (guint8) (scaled & 0xff); + } + self->init_param_updated = FALSE; + self->bitrate_updated = FALSE; + self->rc_param_updated = FALSE; + + if (selected_profile == NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID && + config->frameIntervalP > 1) { + if (downstream_profiles.find ("main") != downstream_profiles.end ()) { + selected_profile = NV_ENC_H264_PROFILE_MAIN_GUID; + } else if (downstream_profiles.find ("high") != downstream_profiles.end ()) { + selected_profile = NV_ENC_H264_PROFILE_HIGH_GUID; + } else if (downstream_profiles.find ("progressive-high") != + downstream_profiles.end ()) { + selected_profile = NV_ENC_H264_PROFILE_PROGRESSIVE_HIGH_GUID; + } + } + + /* Pick the first profile */ + if (selected_profile == NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID) { + if (*downstream_profiles.begin () == "baseline" || + *downstream_profiles.begin () == "constrained-baseline") { + selected_profile = NV_ENC_H264_PROFILE_BASELINE_GUID; + } else if (*downstream_profiles.begin () == "main") { + selected_profile = NV_ENC_H264_PROFILE_MAIN_GUID; + } else if (*downstream_profiles.begin () == "progressive-high") { + selected_profile = NV_ENC_H264_PROFILE_PROGRESSIVE_HIGH_GUID; + } else if (*downstream_profiles.begin () == "constrained-high") { + selected_profile = NV_ENC_H264_PROFILE_CONSTRAINED_HIGH_GUID; + } + } + + config->profileGUID = selected_profile; + + h264_config = &config->encodeCodecConfig.h264Config; + vui = &h264_config->h264VUIParameters; + + h264_config->level = NV_ENC_LEVEL_AUTOSELECT; + h264_config->chromaFormatIDC = 1; + if (selected_profile == NV_ENC_H264_PROFILE_HIGH_444_GUID) + h264_config->chromaFormatIDC = 3; + h264_config->idrPeriod = config->gopLength; + h264_config->outputAUD = self->aud; + if (self->repeat_sequence_header) { + h264_config->disableSPSPPS = 0; + h264_config->repeatSPSPPS = 1; + } else { + if (self->packetized) + h264_config->disableSPSPPS = 1; + else + h264_config->disableSPSPPS = 0; + } + + if (dev_caps->cabac && selected_profile != NV_ENC_H264_PROFILE_BASELINE_GUID) { + if (self->cabac) + h264_config->entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CABAC; + else + h264_config->entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC; + } else { + h264_config->entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_AUTOSELECT; + } + + vui->videoSignalTypePresentFlag = 1; + /* Unspecified */ + vui->videoFormat = 5; + if (info->colorimetry.range == GST_VIDEO_COLOR_RANGE_0_255) { + vui->videoFullRangeFlag = 1; + } else { + vui->videoFullRangeFlag = 0; + } + + vui->colourDescriptionPresentFlag = 1; + vui->colourMatrix = gst_video_color_matrix_to_iso (info->colorimetry.matrix); + vui->colourPrimaries = + gst_video_color_primaries_to_iso (info->colorimetry.primaries); + vui->transferCharacteristics = + gst_video_transfer_function_to_iso (info->colorimetry.transfer); + + g_mutex_unlock (&self->prop_lock); + + if (bframe_aborted) + g_object_notify (G_OBJECT (self), "bframes"); + + return TRUE; +} + +static gboolean +gst_nv_h264_encoder_set_output_state (GstNvEncoder * encoder, + GstVideoCodecState * state, gpointer session) +{ + GstNvH264Encoder *self = GST_NV_H264_ENCODER (encoder); + GstVideoCodecState *output_state; + NV_ENC_SEQUENCE_PARAM_PAYLOAD seq_params = { 0, }; + guint8 spspps[1024]; + guint32 seq_size; + GstCaps *caps; + const gchar *profile_from_sps; + NVENCSTATUS status; + std::set < std::string > downstream_profiles; + std::string caps_str; + GstTagList *tags; + GstBuffer *codec_data = NULL; + GstH264NalUnit sps_nalu, pps_nalu; + GstH264ParserResult rst; + + caps_str = "video/x-h264, alignment = (string) au"; + + gst_nv_h264_encoder_get_downstream_profiles_and_format (self, + downstream_profiles, NULL); + + seq_params.version = gst_nvenc_get_sequence_param_payload_version (); + seq_params.inBufferSize = sizeof (spspps); + seq_params.spsppsBuffer = &spspps; + seq_params.outSPSPPSPayloadSize = &seq_size; + status = NvEncGetSequenceParams (session, &seq_params); + if (status != NV_ENC_SUCCESS) { + GST_ERROR_OBJECT (self, "Failed to get sequence header, status %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + return FALSE; + } + + rst = gst_h264_parser_identify_nalu (self->parser, + spspps, 0, seq_size, &sps_nalu); + if (rst != GST_H264_PARSER_OK) { + GST_ERROR_OBJECT (self, "Failed to identify SPS nal"); + return FALSE; + } + + if (sps_nalu.size < 4) { + GST_ERROR_OBJECT (self, "Too small sps nal size %d", sps_nalu.size); + return FALSE; + } + + rst = gst_h264_parser_identify_nalu_unchecked (self->parser, + spspps, sps_nalu.offset + sps_nalu.size, seq_size, &pps_nalu); + if (rst != GST_H264_PARSER_OK && self->packetized) { + GST_ERROR_OBJECT (self, "Failed to identify PPS nal, %d", rst); + return FALSE; + } + + if (self->packetized) { + GstMapInfo info; + guint8 *data; + guint8 profile_idc, profile_comp, level_idc; + const guint nal_length_size = 4; + const guint num_sps = 1; + const guint num_pps = 1; + + data = sps_nalu.data + sps_nalu.offset + sps_nalu.header_bytes; + profile_idc = data[0]; + profile_comp = data[1]; + level_idc = data[2]; + + /* 5: configuration version, profile, compatibility, level, nal length + * 1: num sps + * 2: sps size bytes + * sizeof (sps) + * 1: num pps + * 2: pps size bytes + * sizeof (pps) + * + * -> 11 + sps_size + pps_size + */ + codec_data = gst_buffer_new_and_alloc (11 + sps_nalu.size + pps_nalu.size); + + gst_buffer_map (codec_data, &info, GST_MAP_WRITE); + + data = (guint8 *) info.data; + data[0] = 1; + data[1] = profile_idc; + data[2] = profile_comp; + data[3] = level_idc; + data[4] = 0xfc | (nal_length_size - 1); + data[5] = 0xe0 | num_sps; + data += 6; + GST_WRITE_UINT16_BE (data, sps_nalu.size); + data += 2; + memcpy (data, sps_nalu.data + sps_nalu.offset, sps_nalu.size); + data += sps_nalu.size; + + data[0] = num_pps; + data++; + + GST_WRITE_UINT16_BE (data, pps_nalu.size); + data += 2; + memcpy (data, pps_nalu.data + pps_nalu.offset, pps_nalu.size); + + gst_buffer_unmap (codec_data, &info); + } + + profile_from_sps = + gst_codec_utils_h264_get_profile (sps_nalu.data + sps_nalu.offset + + sps_nalu.header_bytes, 3); + + if (!profile_from_sps) { + GST_WARNING_OBJECT (self, "Failed to parse profile from SPS"); + } else if (!downstream_profiles.empty ()) { + if (downstream_profiles.find (profile_from_sps) != + downstream_profiles.end ()) { + caps_str += ", profile = (string) " + std::string (profile_from_sps); + } else if (downstream_profiles.find ("baseline") != + downstream_profiles.end () && + strcmp (profile_from_sps, "constrained-baseline") == 0) { + caps_str += ", profile = (string) baseline"; + } else if (downstream_profiles.find ("constrained-baseline") != + downstream_profiles.end () && + strcmp (profile_from_sps, "constrained-baseline") == 0) { + caps_str += ", profile = (string) constrained-baseline"; + } + } else { + caps_str += ", profile = (string) " + std::string (profile_from_sps); + } + + if (self->packetized) { + caps_str += ", stream-format = (string) avc"; + } else { + caps_str += ", stream-format = (string) byte-stream"; + } + + caps = gst_caps_from_string (caps_str.c_str ()); + + if (self->packetized) { + gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, codec_data, NULL); + gst_buffer_unref (codec_data); + } + + output_state = gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (self), + caps, state); + + GST_INFO_OBJECT (self, "Output caps: %" GST_PTR_FORMAT, output_state->caps); + gst_video_codec_state_unref (output_state); + + tags = gst_tag_list_new_empty (); + gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_ENCODER, + "nvh264encoder", NULL); + + gst_video_encoder_merge_tags (GST_VIDEO_ENCODER (encoder), + tags, GST_TAG_MERGE_REPLACE); + gst_tag_list_unref (tags); + + return TRUE; +} + +static GstBuffer * +gst_nv_h264_encoder_create_output_buffer (GstNvEncoder * encoder, + NV_ENC_LOCK_BITSTREAM * bitstream) +{ + GstNvH264Encoder *self = GST_NV_H264_ENCODER (encoder); + GstBuffer *buffer; + GstH264ParserResult rst; + GstH264NalUnit nalu; + + if (!self->packetized) { + return gst_buffer_new_memdup (bitstream->bitstreamBufferPtr, + bitstream->bitstreamSizeInBytes); + } + + buffer = gst_buffer_new (); + rst = gst_h264_parser_identify_nalu (self->parser, + (guint8 *) bitstream->bitstreamBufferPtr, 0, + bitstream->bitstreamSizeInBytes, &nalu); + + if (rst == GST_H264_PARSER_NO_NAL_END) + rst = GST_H264_PARSER_OK; + + while (rst == GST_H264_PARSER_OK) { + GstMemory *mem; + guint8 *data; + + data = (guint8 *) g_malloc0 (nalu.size + 4); + GST_WRITE_UINT32_BE (data, nalu.size); + memcpy (data + 4, nalu.data + nalu.offset, nalu.size); + + mem = gst_memory_new_wrapped ((GstMemoryFlags) 0, data, nalu.size + 4, + 0, nalu.size + 4, data, (GDestroyNotify) g_free); + gst_buffer_append_memory (buffer, mem); + + rst = gst_h264_parser_identify_nalu (self->parser, + (guint8 *) bitstream->bitstreamBufferPtr, nalu.offset + nalu.size, + bitstream->bitstreamSizeInBytes, &nalu); + + if (rst == GST_H264_PARSER_NO_NAL_END) + rst = GST_H264_PARSER_OK; + } + + return buffer; +} + +static GstNvEncoderReconfigure +gst_nv_h264_encoder_check_reconfigure (GstNvEncoder * encoder, + NV_ENC_CONFIG * config) +{ + GstNvH264Encoder *self = GST_NV_H264_ENCODER (encoder); + GstNvEncoderReconfigure reconfig = GST_NV_ENCODER_RECONFIGURE_NONE; + + /* Dynamic RC param update is not tested, do soft-reconfigure only for + * bitrate update */ + g_mutex_lock (&self->prop_lock); + if (self->init_param_updated || self->rc_param_updated) { + reconfig = GST_NV_ENCODER_RECONFIGURE_FULL; + goto done; + } + + if (self->bitrate_updated) { + GstNvH264EncoderClass *klass = GST_NV_H264_ENCODER_GET_CLASS (self); + if (klass->dev_caps.dyn_bitrate_change > 0) { + config->rcParams.averageBitRate = self->bitrate * 1024; + config->rcParams.maxBitRate = self->max_bitrate * 1024; + reconfig = GST_NV_ENCODER_RECONFIGURE_BITRATE; + } else { + reconfig = GST_NV_ENCODER_RECONFIGURE_FULL; + } + } + +done: + self->init_param_updated = FALSE; + self->rc_param_updated = FALSE; + self->bitrate_updated = FALSE; + g_mutex_unlock (&self->prop_lock); + + return reconfig; +} + +static GstNvH264EncoderClassData * +gst_nv_h264_encoder_create_class_data (GstObject * device, gpointer session, + gboolean d3d11_mode) +{ + NVENCSTATUS status; + GstNvH264EncoderDeviceCaps dev_caps = { 0, }; + NV_ENC_CAPS_PARAM caps_param = { 0, }; + GUID profile_guids[16]; + NV_ENC_BUFFER_FORMAT input_formats[16]; + guint32 profile_guid_count = 0; + guint32 input_format_count = 0; + std::string sink_caps_str; + std::string src_caps_str; + std::string format_str; + std::set < std::string > formats; + std::set < std::string > profiles; + std::string profile_str; + std::string resolution_str; + GstNvH264EncoderClassData *cdata; + GstCaps *sink_caps; + GstCaps *system_caps; + + status = NvEncGetEncodeProfileGUIDs (session, NV_ENC_CODEC_H264_GUID, + profile_guids, G_N_ELEMENTS (profile_guids), &profile_guid_count); + if (status != NV_ENC_SUCCESS || profile_guid_count == 0) { + GST_WARNING_OBJECT (device, "Unable to get supported profiles"); + return NULL; + } + + status = NvEncGetInputFormats (session, NV_ENC_CODEC_H264_GUID, input_formats, + G_N_ELEMENTS (input_formats), &input_format_count); + if (status != NV_ENC_SUCCESS || input_format_count == 0) { + GST_WARNING_OBJECT (device, "Unable to get supported input formats"); + return NULL; + } + + 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, NV_ENC_CODEC_H264_GUID, &caps_param, \ + &_val); \ + if (status != NV_ENC_SUCCESS) { \ + GST_WARNING_OBJECT (device, "Unable to query %s, status: %" \ + GST_NVENC_STATUS_FORMAT, G_STRINGIFY (to_query), \ + GST_NVENC_STATUS_ARGS (status)); \ + val = default_val; \ + } else { \ + GST_DEBUG_OBJECT (device, "%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.seperate_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_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 + + for (guint32 i = 0; i < input_format_count; i++) { + switch (input_formats[i]) { + case NV_ENC_BUFFER_FORMAT_NV12: + formats.insert ("NV12"); + break; + case NV_ENC_BUFFER_FORMAT_YUV444: + if (dev_caps.yuv444_encode) + formats.insert ("Y444"); + break; + default: + break; + } + } + + if (formats.empty ()) { + GST_WARNING_OBJECT (device, "Empty supported input format"); + return NULL; + } +#define APPEND_STRING(dst,set,str) G_STMT_START { \ + if (set.find(str) != set.end()) { \ + if (!first) \ + dst += ", "; \ + dst += str; \ + first = false; \ + } \ +} G_STMT_END + + if (formats.size () == 1) { + format_str = "format = (string) " + *(formats.begin ()); + } else { + bool first = true; + + format_str = "format = (string) { "; + APPEND_STRING (format_str, formats, "NV12"); + APPEND_STRING (format_str, formats, "Y444"); + format_str += " }"; + } + + for (guint32 i = 0; i < profile_guid_count; i++) { + if (profile_guids[i] == NV_ENC_H264_PROFILE_BASELINE_GUID) { + profiles.insert ("baseline"); + profiles.insert ("constrained-baseline"); + } else if (profile_guids[i] == NV_ENC_H264_PROFILE_MAIN_GUID) { + profiles.insert ("main"); + } else if (profile_guids[i] == NV_ENC_H264_PROFILE_HIGH_GUID) { + profiles.insert ("high"); + } else if (profile_guids[i] == NV_ENC_H264_PROFILE_HIGH_444_GUID) { + profiles.insert ("high-4:4:4"); + } else if (profile_guids[i] == NV_ENC_H264_PROFILE_PROGRESSIVE_HIGH_GUID) { + profiles.insert ("progressive-high"); + } else if (profile_guids[i] == NV_ENC_H264_PROFILE_CONSTRAINED_HIGH_GUID) { + profiles.insert ("constrained-high"); + } + } + + if (profiles.empty ()) { + GST_WARNING_OBJECT (device, "Empty supported h264 profile"); + return NULL; + } + + if (profiles.size () == 1) { + profile_str = "profile = (string) " + *(profiles.begin ()); + } else { + bool first = true; + + profile_str = "profile = (string) { "; + APPEND_STRING (profile_str, profiles, "main"); + APPEND_STRING (profile_str, profiles, "high"); + APPEND_STRING (profile_str, profiles, "progressive-high"); + APPEND_STRING (profile_str, profiles, "constrained-high"); + APPEND_STRING (profile_str, profiles, "constrained-baseline"); + APPEND_STRING (profile_str, profiles, "baseline"); + APPEND_STRING (profile_str, profiles, "high-4:4:4"); + profile_str += " }"; + } +#undef APPEND_STRING + + resolution_str = "width = (int) [ " + + std::to_string (GST_ROUND_UP_16 (dev_caps.width_min)) + + ", " + std::to_string (dev_caps.width_max) + " ]"; + resolution_str += ", height = (int) [ " + + std::to_string (GST_ROUND_UP_16 (dev_caps.height_min)) + + ", " + std::to_string (dev_caps.height_max) + " ]"; + + sink_caps_str = "video/x-raw, " + format_str + ", " + resolution_str; + + if (dev_caps.field_encoding > 0) { + sink_caps_str += ", interlace-mode = (string) { interleaved, mixed }"; + } else { + sink_caps_str += ", interlace-mode = (string) progressive"; + } + + src_caps_str = "video/x-h264, " + resolution_str + ", " + profile_str + + ", stream-format = (string) { avc, byte-stream }, alignment = (string) au"; + + system_caps = gst_caps_from_string (sink_caps_str.c_str ()); + sink_caps = gst_caps_copy (system_caps); +#ifdef HAVE_NVCODEC_GST_D3D11 + if (d3d11_mode) { + gst_caps_set_features (sink_caps, 0, + gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY, NULL)); + } else +#endif + { + gst_caps_set_features (sink_caps, 0, + gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_CUDA_MEMORY, NULL)); + } + + gst_caps_append (sink_caps, system_caps); + + cdata = g_new0 (GstNvH264EncoderClassData, 1); + cdata->sink_caps = sink_caps; + cdata->src_caps = gst_caps_from_string (src_caps_str.c_str ()); + cdata->dev_caps = dev_caps; + cdata->d3d11_mode = d3d11_mode; + if (d3d11_mode) + g_object_get (device, "adapter-luid", &cdata->adapter_luid, NULL); + else + g_object_get (device, "cuda-device-id", &cdata->cuda_device_id, NULL); + + GST_MINI_OBJECT_FLAG_SET (cdata->sink_caps, + GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); + GST_MINI_OBJECT_FLAG_SET (cdata->src_caps, + GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); + + return cdata; +} + +void +gst_nv_h264_encoder_register_cuda (GstPlugin * plugin, GstCudaContext * context, + guint rank) +{ + NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = { 0, }; + gpointer session; + NVENCSTATUS status; + GstNvH264EncoderClassData *cdata; + + GST_DEBUG_CATEGORY_INIT (gst_nv_h264_encoder_debug, "nvh264encoder", 0, + "nvh264encoder"); + + session_params.version = + gst_nvenc_get_open_encode_session_ex_params_version (); + session_params.deviceType = NV_ENC_DEVICE_TYPE_CUDA; + session_params.device = gst_cuda_context_get_handle (context); + session_params.apiVersion = gst_nvenc_get_api_version (); + + status = NvEncOpenEncodeSessionEx (&session_params, &session); + if (status != NV_ENC_SUCCESS) { + GST_WARNING_OBJECT (context, "Failed to open session"); + return; + } + + cdata = gst_nv_h264_encoder_create_class_data (GST_OBJECT (context), session, + FALSE); + NvEncDestroyEncoder (session); + + if (!cdata) + return; + + GType type; + gchar *type_name; + gchar *feature_name; + GTypeInfo type_info = { + sizeof (GstNvH264EncoderClass), + NULL, + NULL, + (GClassInitFunc) gst_nv_h264_encoder_class_init, + NULL, + cdata, + sizeof (GstNvH264Encoder), + 0, + (GInstanceInitFunc) gst_nv_h264_encoder_init, + }; + + type_name = g_strdup ("GstNvCudaH264Enc"); + feature_name = g_strdup ("nvcudah264enc"); + + gint index = 0; + while (g_type_from_name (type_name)) { + index++; + g_free (type_name); + g_free (feature_name); + type_name = g_strdup_printf ("GstNvCudaH264Device%dEnc", index); + feature_name = g_strdup_printf ("nvcudah264device%denc", index); + } + + type = g_type_register_static (GST_TYPE_NV_ENCODER, type_name, + &type_info, (GTypeFlags) 0); + + if (rank > 0 && index != 0) + rank--; + + if (!gst_element_register (plugin, feature_name, rank, type)) + GST_WARNING ("Failed to register plugin '%s'", type_name); + + g_free (type_name); + g_free (feature_name); +} + +#ifdef HAVE_NVCODEC_GST_D3D11 +void +gst_nv_h264_encoder_register_d3d11 (GstPlugin * plugin, GstD3D11Device * device, + guint rank) +{ + NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = { 0, }; + gpointer session; + NVENCSTATUS status; + GstNvH264EncoderClassData *cdata; + + GST_DEBUG_CATEGORY_INIT (gst_nv_h264_encoder_debug, "nvh264encoder", 0, + "nvh264encoder"); + + session_params.version = + gst_nvenc_get_open_encode_session_ex_params_version (); + session_params.deviceType = NV_ENC_DEVICE_TYPE_DIRECTX; + session_params.device = gst_d3d11_device_get_device_handle (device); + session_params.apiVersion = gst_nvenc_get_api_version (); + + status = NvEncOpenEncodeSessionEx (&session_params, &session); + if (status != NV_ENC_SUCCESS) { + GST_WARNING_OBJECT (device, "Failed to open session"); + return; + } + + cdata = gst_nv_h264_encoder_create_class_data (GST_OBJECT (device), session, + TRUE); + NvEncDestroyEncoder (session); + + if (!cdata) + return; + + GType type; + gchar *type_name; + gchar *feature_name; + GTypeInfo type_info = { + sizeof (GstNvH264EncoderClass), + NULL, + NULL, + (GClassInitFunc) gst_nv_h264_encoder_class_init, + NULL, + cdata, + sizeof (GstNvH264Encoder), + 0, + (GInstanceInitFunc) gst_nv_h264_encoder_init, + }; + + type_name = g_strdup ("GstNvD3D11H264Enc"); + feature_name = g_strdup ("nvd3d11h264enc"); + + gint index = 0; + while (g_type_from_name (type_name)) { + index++; + g_free (type_name); + g_free (feature_name); + type_name = g_strdup_printf ("GstNvD3D11H264Device%dEnc", index); + feature_name = g_strdup_printf ("nvd3d11h264device%denc", index); + } + + type = g_type_register_static (GST_TYPE_NV_ENCODER, type_name, + &type_info, (GTypeFlags) 0); + + if (rank > 0 && index != 0) + rank--; + + if (!gst_element_register (plugin, feature_name, rank, type)) + GST_WARNING ("Failed to register plugin '%s'", type_name); + + g_free (type_name); + g_free (feature_name); +} +#endif diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264encoder.h b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264encoder.h new file mode 100644 index 0000000000..b46abde6e3 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264encoder.h @@ -0,0 +1,37 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * 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. + */ + +#pragma once + +#include "gstnvencoder.h" + +G_BEGIN_DECLS + +void gst_nv_h264_encoder_register_cuda (GstPlugin * plugin, + GstCudaContext * context, + guint rank); + +#ifdef HAVE_NVCODEC_GST_D3D11 +void gst_nv_h264_encoder_register_d3d11 (GstPlugin * plugin, + GstD3D11Device * device, + guint rank); +#endif + + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh265encoder.cpp b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh265encoder.cpp new file mode 100644 index 0000000000..9539c571c5 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh265encoder.cpp @@ -0,0 +1,1939 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * 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 "gstnvh265encoder.h" +#include +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_STATIC (gst_nv_h265_encoder_debug); +#define GST_CAT_DEFAULT gst_nv_h265_encoder_debug + +static GTypeClass *parent_class = NULL; + +typedef struct +{ + gint max_bframes; + gint ratecontrol_modes; + gint field_encoding; + gint monochrome; + gint fmo; + gint qpelmv; + gint bdirect_mode; + gint cabac; + gint adaptive_transform; + gint stereo_mvc; + gint temoral_layers; + gint hierarchical_pframes; + gint hierarchical_bframes; + gint level_max; + gint level_min; + gint seperate_colour_plane; + gint width_max; + gint height_max; + gint temporal_svc; + gint dyn_res_change; + gint dyn_bitrate_change; + gint dyn_force_constqp; + gint dyn_rcmode_change; + gint subframe_readback; + gint constrained_encoding; + gint intra_refresh; + gint custom_vbv_buf_size; + gint dynamic_slice_mode; + gint ref_pic_invalidation; + gint preproc_support; + gint async_encoding_support; + gint mb_num_max; + gint mb_per_sec_max; + gint yuv444_encode; + gint lossless_encode; + gint sao; + gint meonly_mode; + gint lookahead; + gint temporal_aq; + gint supports_10bit_encode; + gint num_max_ltr_frames; + gint weighted_prediction; + gint bframe_ref_mode; + gint emphasis_level_map; + gint width_min; + gint height_min; + gint multiple_ref_frames; +} GstNvH265EncoderDeviceCaps; + +typedef struct +{ + GstCaps *sink_caps; + GstCaps *src_caps; + + guint cuda_device_id; + gint64 adapter_luid; + gboolean d3d11_mode; + + GstNvH265EncoderDeviceCaps dev_caps; +} GstNvH265EncoderClassData; + +enum +{ + PROP_0, + PROP_ADAPTER_LUID, + PROP_CUDA_DEVICE_ID, + + /* init params */ + PROP_PRESET, + PROP_WEIGHTED_PRED, + + /* encoding config */ + PROP_GOP_SIZE, + PROP_B_FRAMES, + + /* rate-control params */ + PROP_RC_MODE, + + PROP_QP_CONST_I, + PROP_QP_CONST_P, + PROP_QP_CONST_B, + + PROP_BITRATE, + PROP_MAX_BITRATE, + PROP_VBV_BUFFER_SIZE, + + PROP_RC_LOOKAHEAD, + PROP_I_ADAPT, + PROP_B_ADAPT, + PROP_SPATIAL_AQ, + PROP_TEMPORAL_AQ, + PROP_ZERO_LATENCY, + PROP_NON_REF_P, + PROP_STRICT_GOP, + PROP_AQ_STRENGTH, + + PROP_QP_MIN_I, + PROP_QP_MIN_P, + PROP_QP_MIN_B, + + PROP_QP_MAX_I, + PROP_QP_MAX_P, + PROP_QP_MAX_B, + + PROP_CONST_QUALITY, + + /* h265 specific */ + PROP_AUD, + PROP_REPEAT_SEQUENCE_HEADER, +}; + +#define DEFAULT_PRESET GST_NV_ENCODER_PRESET_DEFAULT +#define DEFAULT_WEIGHTED_PRED FALSE +#define DEFAULT_GOP_SIZE 75 +#define DEFAULT_B_FRAMES 0 +#define DEFAULT_RC_MODE GST_NV_ENCODER_RC_MODE_VBR +#define DEFAULT_QP -1 +#define DEFAULT_BITRATE 0 +#define DEFAULT_MAX_BITRATE 0 +#define DEFAULT_VBV_BUFFER_SIZE 0 +#define DEFAULT_RC_LOOKAHEAD 0 +#define DEFAULT_I_ADAPT FALSE +#define DEFAULT_B_ADAPT FALSE +#define DEFAULT_SPATIAL_AQ FALSE +#define DEFAULT_TEMPORAL_AQ FALSE +#define DEFAULT_ZERO_LATENCY FALSE +#define DEFAULT_NON_REF_P FALSE +#define DEFAULT_STRICT_GOP FALSE +#define DEFAULT_AQ_STRENGTH FALSE +#define DEFAULT_CONST_QUALITY 0 +#define DEFAULT_AUD TRUE +#define DEFAULT_REPEAT_SEQUENCE_HEADER FALSE + +typedef enum +{ + GST_NV_H265_ENCODER_BYTE_STREAM, + GST_NV_H265_ENCODER_HVC1, + GST_NV_H265_ENCODER_HEV1, +} GstNvH265EncoderStreamFormat; + +typedef struct _GstNvH265Encoder +{ + GstNvEncoder parent; + GMutex prop_lock; + + gboolean init_param_updated; + gboolean rc_param_updated; + gboolean bitrate_updated; + + GstNvH265EncoderStreamFormat stream_format; + GstH265Parser *parser; + + /* Properties */ + GstNvEncoderPreset preset; + gboolean weighted_pred; + + gint gop_size; + guint bframes; + + GstNvEncoderRCMode rc_mode; + gint qp_const_i; + gint qp_const_p; + gint qp_const_b; + guint bitrate; + guint max_bitrate; + guint vbv_buffer_size; + guint rc_lookahead; + gboolean i_adapt; + gboolean b_adapt; + gboolean spatial_aq; + gboolean temporal_aq; + gboolean zero_latency; + gboolean non_ref_p; + gboolean strict_gop; + guint aq_strength; + gint qp_min_i; + gint qp_min_p; + gint qp_min_b; + gint qp_max_i; + gint qp_max_p; + gint qp_max_b; + gdouble const_quality; + + gboolean aud; + gboolean repeat_sequence_header; +} GstNvH265Encoder; + +typedef struct _GstNvH265EncoderClass +{ + GstNvEncoderClass parent_class; + + GstNvH265EncoderDeviceCaps dev_caps; + + guint cuda_device_id; + gint64 adapter_luid; + gboolean d3d11_mode; +} GstNvH265EncoderClass; + +#define GST_NV_H265_ENCODER(object) ((GstNvH265Encoder *) (object)) +#define GST_NV_H265_ENCODER_GET_CLASS(object) \ + (G_TYPE_INSTANCE_GET_CLASS ((object),G_TYPE_FROM_INSTANCE (object),GstNvH265EncoderClass)) + +static void gst_nv_h265_encoder_finalize (GObject * object); +static void gst_nv_h265_encoder_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_nv_h265_encoder_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static GstCaps *gst_nv_h265_encoder_getcaps (GstVideoEncoder * encoder, + GstCaps * filter); +static gboolean gst_nv_h265_encoder_set_format (GstNvEncoder * encoder, + GstVideoCodecState * state, gpointer session, + NV_ENC_INITIALIZE_PARAMS * init_params, NV_ENC_CONFIG * config); +static gboolean gst_nv_h265_encoder_set_output_state (GstNvEncoder * encoder, + GstVideoCodecState * state, gpointer session); +static GstBuffer *gst_nv_h265_encoder_create_output_buffer (GstNvEncoder * + encoder, NV_ENC_LOCK_BITSTREAM * bitstream); +static GstNvEncoderReconfigure +gst_nv_h265_encoder_check_reconfigure (GstNvEncoder * encoder, + NV_ENC_CONFIG * config); + +static void +gst_nv_h265_encoder_class_init (GstNvH265EncoderClass * klass, gpointer data) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstVideoEncoderClass *videoenc_class = GST_VIDEO_ENCODER_CLASS (klass); + GstNvEncoderClass *nvenc_class = GST_NV_ENCODER_CLASS (klass); + GstNvH265EncoderClassData *cdata = (GstNvH265EncoderClassData *) data; + GstNvH265EncoderDeviceCaps *dev_caps = &cdata->dev_caps; + GParamFlags param_flags = (GParamFlags) (G_PARAM_READWRITE | + GST_PARAM_MUTABLE_PLAYING | G_PARAM_STATIC_STRINGS); + GParamFlags conditonal_param_flags = (GParamFlags) (G_PARAM_READWRITE | + GST_PARAM_CONDITIONALLY_AVAILABLE | GST_PARAM_MUTABLE_PLAYING | + G_PARAM_STATIC_STRINGS); + + parent_class = (GTypeClass *) g_type_class_peek_parent (klass); + + object_class->finalize = gst_nv_h265_encoder_finalize; + object_class->set_property = gst_nv_h265_encoder_set_property; + object_class->get_property = gst_nv_h265_encoder_get_property; + + if (cdata->d3d11_mode) { + g_object_class_install_property (object_class, PROP_ADAPTER_LUID, + g_param_spec_int64 ("adapter-luid", "Adapter LUID", + "DXGI Adapter LUID (Locally Unique Identifier) of associated GPU", + G_MININT64, G_MAXINT64, cdata->adapter_luid, + (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + } else { + g_object_class_install_property (object_class, PROP_CUDA_DEVICE_ID, + g_param_spec_uint ("cuda-device-id", "CUDA Device ID", + "CUDA device ID of associated GPU", + 0, G_MAXINT, cdata->cuda_device_id, + (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + } + g_object_class_install_property (object_class, PROP_PRESET, + g_param_spec_enum ("preset", "Encoding Preset", + "Encoding Preset", GST_TYPE_NV_ENCODER_PRESET, + DEFAULT_PRESET, param_flags)); + if (dev_caps->weighted_prediction) { + g_object_class_install_property (object_class, PROP_WEIGHTED_PRED, + g_param_spec_boolean ("weighted-pred", "Weighted Pred", + "Enables Weighted Prediction", DEFAULT_WEIGHTED_PRED, + conditonal_param_flags)); + } + g_object_class_install_property (object_class, PROP_GOP_SIZE, + g_param_spec_int ("gop-size", "GOP size", + "Number of frames between intra frames (-1 = infinite)", + -1, G_MAXINT, DEFAULT_GOP_SIZE, param_flags)); + if (dev_caps->max_bframes > 0) { + g_object_class_install_property (object_class, PROP_B_FRAMES, + g_param_spec_uint ("bframes", "B-Frames", + "Number of B-frames between I and P", 0, dev_caps->max_bframes, + DEFAULT_B_FRAMES, conditonal_param_flags)); + } + g_object_class_install_property (object_class, PROP_RC_MODE, + g_param_spec_enum ("rc-mode", "RC Mode", "Rate Control Mode", + GST_TYPE_NV_ENCODER_RC_MODE, DEFAULT_RC_MODE, param_flags)); + g_object_class_install_property (object_class, PROP_QP_CONST_I, + g_param_spec_int ("qp-const-i", "QP Const I", + "Constant QP value for I frame (-1 = disabled)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_CONST_P, + g_param_spec_int ("qp-const-p", "QP Const P", + "Constant QP value for P frame (-1 = disabled)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_CONST_B, + g_param_spec_int ("qp-const-b", "QP Const B", + "Constant QP value for B frame (-1 = disabled)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_BITRATE, + g_param_spec_uint ("bitrate", "Bitrate", + "Bitrate in kbit/sec (0 = automatic)", 0, 2000 * 1024, + DEFAULT_BITRATE, param_flags)); + g_object_class_install_property (object_class, PROP_MAX_BITRATE, + g_param_spec_uint ("max-bitrate", "Max Bitrate", + "Maximum Bitrate in kbit/sec (ignored in CBR mode)", 0, 2000 * 1024, + DEFAULT_MAX_BITRATE, param_flags)); + if (dev_caps->custom_vbv_buf_size) { + g_object_class_install_property (object_class, + PROP_VBV_BUFFER_SIZE, + g_param_spec_uint ("vbv-buffer-size", "VBV Buffer Size", + "VBV(HRD) Buffer Size in kbits (0 = NVENC default)", + 0, G_MAXUINT, DEFAULT_VBV_BUFFER_SIZE, conditonal_param_flags)); + } + if (dev_caps->lookahead) { + g_object_class_install_property (object_class, PROP_RC_LOOKAHEAD, + g_param_spec_uint ("rc-lookahead", "Rate Control Lookahead", + "Number of frames for frame type lookahead", + 0, 32, DEFAULT_RC_LOOKAHEAD, conditonal_param_flags)); + g_object_class_install_property (object_class, PROP_I_ADAPT, + g_param_spec_boolean ("i-adapt", "I Adapt", + "Enable adaptive I-frame insert when lookahead is enabled", + DEFAULT_I_ADAPT, conditonal_param_flags)); + if (dev_caps->max_bframes > 0) { + g_object_class_install_property (object_class, PROP_B_ADAPT, + g_param_spec_boolean ("b-adapt", "B Adapt", + "Enable adaptive B-frame insert when lookahead is enabled", + DEFAULT_B_ADAPT, conditonal_param_flags)); + } + } + g_object_class_install_property (object_class, PROP_SPATIAL_AQ, + g_param_spec_boolean ("spatial-aq", "Spatial AQ", + "Spatial Adaptive Quantization", DEFAULT_SPATIAL_AQ, param_flags)); + if (dev_caps->temporal_aq) { + g_object_class_install_property (object_class, PROP_TEMPORAL_AQ, + g_param_spec_boolean ("temporal-aq", "Temporal AQ", + "Temporal Adaptive Quantization", DEFAULT_TEMPORAL_AQ, + conditonal_param_flags)); + } + g_object_class_install_property (object_class, PROP_ZERO_LATENCY, + g_param_spec_boolean ("zerolatency", "Zerolatency", + "Zero latency operation (no reordering delay)", DEFAULT_ZERO_LATENCY, + param_flags)); + g_object_class_install_property (object_class, PROP_NON_REF_P, + g_param_spec_boolean ("nonref-p", "Nonref P", + "Automatic insertion of non-reference P-frames", DEFAULT_NON_REF_P, + param_flags)); + g_object_class_install_property (object_class, PROP_STRICT_GOP, + g_param_spec_boolean ("strict-gop", "Strict GOP", + "Minimize GOP-to-GOP rate fluctuations", DEFAULT_STRICT_GOP, + param_flags)); + g_object_class_install_property (object_class, PROP_AQ_STRENGTH, + g_param_spec_uint ("aq-strength", "AQ Strength", + "Adaptive Quantization Strength when spatial-aq is enabled" + " from 1 (low) to 15 (aggressive), (0 = autoselect)", + 0, 15, DEFAULT_AQ_STRENGTH, param_flags)); + g_object_class_install_property (object_class, PROP_QP_MIN_I, + g_param_spec_int ("qp-min-i", "QP Min I", + "Minimum QP value for I frame, (-1 = disabled)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_MIN_P, + g_param_spec_int ("qp-min-p", "QP Min P", + "Minimum QP value for P frame, (-1 = automatic)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_MIN_B, + g_param_spec_int ("qp-min-b", "QP Min B", + "Minimum QP value for B frame, (-1 = automatic)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_MAX_I, + g_param_spec_int ("qp-max-i", "QP Max I", + "Maximum QP value for I frame, (-1 = disabled)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_MAX_P, + g_param_spec_int ("qp-max-p", "QP Max P", + "Maximum QP value for P frame, (-1 = automatic)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_QP_MAX_B, + g_param_spec_int ("qp-max-b", "QP Max B", + "Maximum QP value for B frame, (-1 = automatic)", -1, 51, + DEFAULT_QP, param_flags)); + g_object_class_install_property (object_class, PROP_CONST_QUALITY, + g_param_spec_double ("const-quality", "Constant Quality", + "Target Constant Quality level for VBR mode (0 = automatic)", + 0, 51, DEFAULT_CONST_QUALITY, param_flags)); + g_object_class_install_property (object_class, PROP_AUD, + g_param_spec_boolean ("aud", "AUD", + "Use AU (Access Unit) delimiter", DEFAULT_AUD, param_flags)); + g_object_class_install_property (object_class, PROP_REPEAT_SEQUENCE_HEADER, + g_param_spec_boolean ("repeat-sequence-header", "Repeat Sequence Header", + "Insert sequence headers (SPS/PPS) per IDR, " + "ignored if negotiated stream-format is \"hvc1\"", + DEFAULT_REPEAT_SEQUENCE_HEADER, param_flags)); + + if (cdata->d3d11_mode) { + gst_element_class_set_metadata (element_class, + "NVENC H.265 Video Encoder Direct3D11 Mode", + "Codec/Encoder/Video/Hardware", + "Encode H.265 video streams using NVCODEC API Direct3D11 Mode", + "Seungha Yang "); + } else { + gst_element_class_set_metadata (element_class, + "NVENC H.265 Video Encoder CUDA Mode", + "Codec/Encoder/Video/Hardware", + "Encode H.265 video streams using NVCODEC API CUDA Mode", + "Seungha Yang "); + } + + gst_element_class_add_pad_template (element_class, + gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + cdata->sink_caps)); + gst_element_class_add_pad_template (element_class, + gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + cdata->src_caps)); + + videoenc_class->getcaps = GST_DEBUG_FUNCPTR (gst_nv_h265_encoder_getcaps); + + nvenc_class->set_format = GST_DEBUG_FUNCPTR (gst_nv_h265_encoder_set_format); + nvenc_class->set_output_state = + GST_DEBUG_FUNCPTR (gst_nv_h265_encoder_set_output_state); + nvenc_class->create_output_buffer = + GST_DEBUG_FUNCPTR (gst_nv_h265_encoder_create_output_buffer); + nvenc_class->check_reconfigure = + GST_DEBUG_FUNCPTR (gst_nv_h265_encoder_check_reconfigure); + + klass->dev_caps = cdata->dev_caps; + klass->cuda_device_id = cdata->cuda_device_id; + klass->adapter_luid = cdata->adapter_luid; + klass->d3d11_mode = cdata->d3d11_mode; + + gst_caps_unref (cdata->sink_caps); + gst_caps_unref (cdata->src_caps); + g_free (cdata); +} + +static void +gst_nv_h265_encoder_init (GstNvH265Encoder * self) +{ + GstNvH265EncoderClass *klass = GST_NV_H265_ENCODER_GET_CLASS (self); + + g_mutex_init (&self->prop_lock); + + self->preset = DEFAULT_PRESET; + self->weighted_pred = DEFAULT_WEIGHTED_PRED; + self->gop_size = DEFAULT_GOP_SIZE; + self->bframes = DEFAULT_B_FRAMES; + self->rc_mode = DEFAULT_RC_MODE; + self->qp_const_i = DEFAULT_QP; + self->qp_const_p = DEFAULT_QP; + self->qp_const_b = DEFAULT_QP; + self->bitrate = DEFAULT_BITRATE; + self->max_bitrate = DEFAULT_MAX_BITRATE; + self->vbv_buffer_size = DEFAULT_VBV_BUFFER_SIZE; + self->rc_lookahead = DEFAULT_RC_LOOKAHEAD; + self->i_adapt = DEFAULT_I_ADAPT; + self->b_adapt = DEFAULT_B_ADAPT; + self->spatial_aq = DEFAULT_SPATIAL_AQ; + self->temporal_aq = DEFAULT_TEMPORAL_AQ; + self->zero_latency = DEFAULT_ZERO_LATENCY; + self->non_ref_p = DEFAULT_NON_REF_P; + self->strict_gop = DEFAULT_STRICT_GOP; + self->aq_strength = DEFAULT_AQ_STRENGTH; + self->qp_min_i = DEFAULT_QP; + self->qp_min_p = DEFAULT_QP; + self->qp_min_b = DEFAULT_QP; + self->qp_max_i = DEFAULT_QP; + self->qp_max_p = DEFAULT_QP; + self->qp_max_b = DEFAULT_QP; + self->const_quality = DEFAULT_CONST_QUALITY; + self->aud = DEFAULT_AUD; + self->repeat_sequence_header = DEFAULT_REPEAT_SEQUENCE_HEADER; + + self->parser = gst_h265_parser_new (); + + if (klass->d3d11_mode) { + gst_nv_encoder_set_dxgi_adapter_luid (GST_NV_ENCODER (self), + klass->adapter_luid); + } else { + gst_nv_encoder_set_cuda_device_id (GST_NV_ENCODER (self), + klass->cuda_device_id); + } +} + +static void +gst_nv_h265_encoder_finalize (GObject * object) +{ + GstNvH265Encoder *self = GST_NV_H265_ENCODER (object); + + g_mutex_clear (&self->prop_lock); + gst_h265_parser_free (self->parser); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +typedef enum +{ + UPDATE_INIT_PARAM, + UPDATE_RC_PARAM, + UPDATE_BITRATE, +} PropUpdateLevel; + +static void +update_boolean (GstNvH265Encoder * self, gboolean * old_val, + const GValue * new_val, PropUpdateLevel level) +{ + gboolean val = g_value_get_boolean (new_val); + + if (*old_val == val) + return; + + *old_val = val; + switch (level) { + case UPDATE_INIT_PARAM: + self->init_param_updated = TRUE; + break; + case UPDATE_RC_PARAM: + self->rc_param_updated = TRUE; + break; + case UPDATE_BITRATE: + self->bitrate_updated = TRUE; + break; + } +} + +static void +update_int (GstNvH265Encoder * self, gint * old_val, + const GValue * new_val, PropUpdateLevel level) +{ + gint val = g_value_get_int (new_val); + + if (*old_val == val) + return; + + *old_val = val; + switch (level) { + case UPDATE_INIT_PARAM: + self->init_param_updated = TRUE; + break; + case UPDATE_RC_PARAM: + self->rc_param_updated = TRUE; + break; + case UPDATE_BITRATE: + self->bitrate_updated = TRUE; + break; + } +} + +static void +update_uint (GstNvH265Encoder * self, guint * old_val, + const GValue * new_val, PropUpdateLevel level) +{ + guint val = g_value_get_uint (new_val); + + if (*old_val == val) + return; + + *old_val = val; + switch (level) { + case UPDATE_INIT_PARAM: + self->init_param_updated = TRUE; + break; + case UPDATE_RC_PARAM: + self->rc_param_updated = TRUE; + break; + case UPDATE_BITRATE: + self->bitrate_updated = TRUE; + break; + } +} + +static void +update_double (GstNvH265Encoder * self, gdouble * old_val, + const GValue * new_val, PropUpdateLevel level) +{ + gdouble val = g_value_get_double (new_val); + + if (*old_val == val) + return; + + *old_val = val; + switch (level) { + case UPDATE_INIT_PARAM: + self->init_param_updated = TRUE; + break; + case UPDATE_RC_PARAM: + self->rc_param_updated = TRUE; + break; + case UPDATE_BITRATE: + self->bitrate_updated = TRUE; + break; + } +} + +static void +gst_nv_h265_encoder_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstNvH265Encoder *self = GST_NV_H265_ENCODER (object); + + g_mutex_lock (&self->prop_lock); + switch (prop_id) { + case PROP_PRESET:{ + GstNvEncoderPreset preset = (GstNvEncoderPreset) g_value_get_enum (value); + if (preset != self->preset) { + self->preset = preset; + self->init_param_updated = TRUE; + } + break; + } + case PROP_WEIGHTED_PRED: + update_boolean (self, &self->weighted_pred, value, UPDATE_INIT_PARAM); + break; + case PROP_GOP_SIZE: + update_int (self, &self->gop_size, value, UPDATE_INIT_PARAM); + break; + case PROP_B_FRAMES: + update_uint (self, &self->bframes, value, UPDATE_INIT_PARAM); + break; + case PROP_RC_MODE:{ + GstNvEncoderRCMode mode = (GstNvEncoderRCMode) g_value_get_enum (value); + if (mode != self->rc_mode) { + self->rc_mode = mode; + self->rc_param_updated = TRUE; + } + break; + } + case PROP_QP_CONST_I: + update_int (self, &self->qp_const_i, value, UPDATE_RC_PARAM); + break; + case PROP_QP_CONST_P: + update_int (self, &self->qp_const_p, value, UPDATE_RC_PARAM); + break; + case PROP_QP_CONST_B: + update_int (self, &self->qp_const_b, value, UPDATE_RC_PARAM); + break; + case PROP_BITRATE: + update_uint (self, &self->bitrate, value, UPDATE_BITRATE); + break; + case PROP_MAX_BITRATE: + update_uint (self, &self->max_bitrate, value, UPDATE_BITRATE); + break; + case PROP_VBV_BUFFER_SIZE: + update_uint (self, &self->vbv_buffer_size, value, UPDATE_RC_PARAM); + break; + case PROP_RC_LOOKAHEAD: + /* rc-lookahead update requires pool size change */ + update_uint (self, &self->rc_lookahead, value, UPDATE_INIT_PARAM); + break; + case PROP_I_ADAPT: + update_boolean (self, &self->i_adapt, value, UPDATE_RC_PARAM); + break; + case PROP_B_ADAPT: + update_boolean (self, &self->b_adapt, value, UPDATE_RC_PARAM); + break; + case PROP_SPATIAL_AQ: + update_boolean (self, &self->spatial_aq, value, UPDATE_RC_PARAM); + break; + case PROP_TEMPORAL_AQ: + update_boolean (self, &self->temporal_aq, value, UPDATE_RC_PARAM); + break; + case PROP_ZERO_LATENCY: + update_boolean (self, &self->zero_latency, value, UPDATE_RC_PARAM); + break; + case PROP_NON_REF_P: + update_boolean (self, &self->non_ref_p, value, UPDATE_RC_PARAM); + break; + case PROP_STRICT_GOP: + update_boolean (self, &self->strict_gop, value, UPDATE_RC_PARAM); + break; + case PROP_AQ_STRENGTH: + update_uint (self, &self->aq_strength, value, UPDATE_RC_PARAM); + break; + case PROP_QP_MIN_I: + update_int (self, &self->qp_min_i, value, UPDATE_RC_PARAM); + break; + case PROP_QP_MIN_P: + update_int (self, &self->qp_min_p, value, UPDATE_RC_PARAM); + break; + case PROP_QP_MIN_B: + update_int (self, &self->qp_min_b, value, UPDATE_RC_PARAM); + break; + case PROP_QP_MAX_I: + update_int (self, &self->qp_min_i, value, UPDATE_RC_PARAM); + break; + case PROP_QP_MAX_P: + update_int (self, &self->qp_min_p, value, UPDATE_RC_PARAM); + break; + case PROP_QP_MAX_B: + update_int (self, &self->qp_min_b, value, UPDATE_RC_PARAM); + break; + case PROP_CONST_QUALITY: + update_double (self, &self->const_quality, value, UPDATE_RC_PARAM); + break; + case PROP_AUD: + update_boolean (self, &self->aud, value, UPDATE_INIT_PARAM); + break; + case PROP_REPEAT_SEQUENCE_HEADER: + update_boolean (self, + &self->repeat_sequence_header, value, UPDATE_INIT_PARAM); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + g_mutex_unlock (&self->prop_lock); +} + +static void +gst_nv_h265_encoder_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstNvH265Encoder *self = GST_NV_H265_ENCODER (object); + GstNvH265EncoderClass *klass = GST_NV_H265_ENCODER_GET_CLASS (self); + + switch (prop_id) { + case PROP_ADAPTER_LUID: + g_value_set_int64 (value, klass->adapter_luid); + break; + case PROP_CUDA_DEVICE_ID: + g_value_set_uint (value, klass->cuda_device_id); + break; + case PROP_PRESET: + g_value_set_enum (value, self->preset); + break; + case PROP_WEIGHTED_PRED: + g_value_set_boolean (value, self->weighted_pred); + break; + case PROP_GOP_SIZE: + g_value_set_int (value, self->gop_size); + break; + case PROP_B_FRAMES: + g_value_set_uint (value, self->bframes); + break; + case PROP_RC_MODE: + g_value_set_enum (value, self->rc_mode); + break; + case PROP_QP_CONST_I: + g_value_set_int (value, self->qp_const_i); + break; + case PROP_QP_CONST_P: + g_value_set_int (value, self->qp_const_p); + break; + case PROP_QP_CONST_B: + g_value_set_int (value, self->qp_const_b); + break; + case PROP_BITRATE: + g_value_set_uint (value, self->bitrate); + break; + case PROP_MAX_BITRATE: + g_value_set_uint (value, self->max_bitrate); + break; + case PROP_VBV_BUFFER_SIZE: + g_value_set_uint (value, self->vbv_buffer_size); + break; + case PROP_RC_LOOKAHEAD: + g_value_set_uint (value, self->rc_lookahead); + break; + case PROP_I_ADAPT: + g_value_set_boolean (value, self->i_adapt); + break; + case PROP_B_ADAPT: + g_value_set_boolean (value, self->b_adapt); + break; + case PROP_SPATIAL_AQ: + g_value_set_boolean (value, self->spatial_aq); + break; + case PROP_TEMPORAL_AQ: + g_value_set_boolean (value, self->temporal_aq); + break; + case PROP_ZERO_LATENCY: + g_value_set_boolean (value, self->zero_latency); + break; + case PROP_NON_REF_P: + g_value_set_boolean (value, self->non_ref_p); + break; + case PROP_STRICT_GOP: + g_value_set_boolean (value, self->strict_gop); + break; + case PROP_AQ_STRENGTH: + g_value_set_uint (value, self->aq_strength); + break; + case PROP_QP_MIN_I: + g_value_set_int (value, self->qp_min_i); + break; + case PROP_QP_MIN_P: + g_value_set_int (value, self->qp_min_p); + break; + case PROP_QP_MIN_B: + g_value_set_int (value, self->qp_min_b); + break; + case PROP_QP_MAX_I: + g_value_set_int (value, self->qp_max_i); + break; + case PROP_QP_MAX_P: + g_value_set_int (value, self->qp_max_p); + break; + case PROP_QP_MAX_B: + g_value_set_int (value, self->qp_max_b); + break; + case PROP_CONST_QUALITY: + g_value_set_double (value, self->const_quality); + break; + case PROP_AUD: + g_value_set_boolean (value, self->aud); + break; + case PROP_REPEAT_SEQUENCE_HEADER: + g_value_set_boolean (value, self->repeat_sequence_header); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_nv_h265_encoder_get_downstream_profiles_and_format (GstNvH265Encoder * self, + std::set < std::string > &downstream_profiles, + GstNvH265EncoderStreamFormat * format) +{ + GstCaps *allowed_caps; + GstStructure *s; + const gchar *stream_format; + + allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (self)); + + if (!allowed_caps || gst_caps_is_empty (allowed_caps) || + gst_caps_is_any (allowed_caps)) { + gst_clear_caps (&allowed_caps); + + return; + } + + for (guint i = 0; i < gst_caps_get_size (allowed_caps); i++) { + const GValue *profile_value; + const gchar *profile; + + s = gst_caps_get_structure (allowed_caps, i); + profile_value = gst_structure_get_value (s, "profile"); + if (!profile_value) + continue; + + if (GST_VALUE_HOLDS_LIST (profile_value)) { + for (guint j = 0; j < gst_value_list_get_size (profile_value); j++) { + const GValue *p = gst_value_list_get_value (profile_value, j); + + if (!G_VALUE_HOLDS_STRING (p)) + continue; + + profile = g_value_get_string (p); + if (profile) + downstream_profiles.insert (profile); + } + + } else if (G_VALUE_HOLDS_STRING (profile_value)) { + profile = g_value_get_string (profile_value); + if (profile) + downstream_profiles.insert (profile); + } + } + + if (format) { + *format = GST_NV_H265_ENCODER_BYTE_STREAM; + + allowed_caps = gst_caps_fixate (allowed_caps); + s = gst_caps_get_structure (allowed_caps, 0); + stream_format = gst_structure_get_string (s, "stream-format"); + if (g_strcmp0 (stream_format, "hvc1") == 0) + *format = GST_NV_H265_ENCODER_HVC1; + else if (g_strcmp0 (stream_format, "hev1") == 0) + *format = GST_NV_H265_ENCODER_HEV1; + } + + gst_caps_unref (allowed_caps); +} + +static GstCaps * +gst_nv_h265_encoder_getcaps (GstVideoEncoder * encoder, GstCaps * filter) +{ + GstNvH265Encoder *self = GST_NV_H265_ENCODER (encoder); + GstCaps *template_caps; + GstCaps *supported_caps; + std::set < std::string > downstream_profiles; + std::set < std::string > allowed_formats; + + gst_nv_h265_encoder_get_downstream_profiles_and_format (self, + downstream_profiles, NULL); + + GST_DEBUG_OBJECT (self, "Downstream specified %" G_GSIZE_FORMAT " profiles", + downstream_profiles.size ()); + + if (downstream_profiles.size () == 0) + return gst_video_encoder_proxy_getcaps (encoder, NULL, filter); + + /* *INDENT-OFF* */ + for (const auto &iter: downstream_profiles) { + if (iter == "main") { + allowed_formats.insert("NV12"); + } else if (iter == "main-10") { + allowed_formats.insert("P010_10LE"); + } else if (iter == "main-444") { + allowed_formats.insert("Y444"); + } else if (iter == "main-444-10") { + allowed_formats.insert("Y444_16LE"); + } + } + /* *INDENT-ON* */ + + template_caps = gst_pad_get_pad_template_caps (encoder->sinkpad); + template_caps = gst_caps_make_writable (template_caps); + + GValue formats = G_VALUE_INIT; + + g_value_init (&formats, GST_TYPE_LIST); + /* *INDENT-OFF* */ + for (const auto &iter: allowed_formats) { + GValue val = G_VALUE_INIT; + g_value_init (&val, G_TYPE_STRING); + + g_value_set_string (&val, iter.c_str()); + gst_value_list_append_and_take_value (&formats, &val); + } + /* *INDENT-ON* */ + + gst_caps_set_value (template_caps, "format", &formats); + g_value_unset (&formats); + + supported_caps = gst_video_encoder_proxy_getcaps (encoder, + template_caps, filter); + gst_caps_unref (template_caps); + + GST_DEBUG_OBJECT (self, "Returning %" GST_PTR_FORMAT, supported_caps); + + return supported_caps; +} + +static gboolean +gst_nv_h265_encoder_set_format (GstNvEncoder * encoder, + GstVideoCodecState * state, gpointer session, + NV_ENC_INITIALIZE_PARAMS * init_params, NV_ENC_CONFIG * config) +{ + GstNvH265Encoder *self = GST_NV_H265_ENCODER (encoder); + GstNvH265EncoderClass *klass = GST_NV_H265_ENCODER_GET_CLASS (self); + GstNvH265EncoderDeviceCaps *dev_caps = &klass->dev_caps; + NV_ENC_RC_PARAMS *rc_params; + GstVideoInfo *info = &state->info; + NVENCSTATUS status; + NV_ENC_PRESET_CONFIG preset_config = { 0, }; + gint dar_n, dar_d; + GstNvEncoderRCMode rc_mode; + NV_ENC_CONFIG_HEVC *hevc_config; + NV_ENC_CONFIG_HEVC_VUI_PARAMETERS *vui; + std::set < std::string > downstream_profiles; + GUID selected_profile = NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID; + guint chroma_format_index = 1; + guint bitdepth_minus8 = 0; + + self->stream_format = GST_NV_H265_ENCODER_BYTE_STREAM; + + gst_nv_h265_encoder_get_downstream_profiles_and_format (self, + downstream_profiles, &self->stream_format); + + if (downstream_profiles.empty ()) { + GST_ERROR_OBJECT (self, "Unable to get downstream profile"); + return FALSE; + } + + /* XXX: we may need to relax condition a litte */ + switch (GST_VIDEO_INFO_FORMAT (info)) { + case GST_VIDEO_FORMAT_NV12: + if (downstream_profiles.find ("main") == downstream_profiles.end ()) { + GST_ERROR_OBJECT (self, "Downstream does not support main profile"); + return FALSE; + } else { + selected_profile = NV_ENC_HEVC_PROFILE_MAIN_GUID; + } + break; + case GST_VIDEO_FORMAT_P010_10LE: + if (downstream_profiles.find ("main-10") == downstream_profiles.end ()) { + GST_ERROR_OBJECT (self, "Downstream does not support main profile"); + return FALSE; + } else { + selected_profile = NV_ENC_HEVC_PROFILE_MAIN10_GUID; + bitdepth_minus8 = 2; + } + break; + case GST_VIDEO_FORMAT_Y444: + if (downstream_profiles.find ("main-444") == downstream_profiles.end ()) { + GST_ERROR_OBJECT (self, "Downstream does not support 4:4:4 profile"); + return FALSE; + } else { + selected_profile = NV_ENC_HEVC_PROFILE_FREXT_GUID; + chroma_format_index = 3; + } + break; + case GST_VIDEO_FORMAT_Y444_16LE: + if (downstream_profiles.find ("main-444-10") == + downstream_profiles.end ()) { + GST_ERROR_OBJECT (self, + "Downstream does not support 4:4:4 10bits profile"); + return FALSE; + } else { + selected_profile = NV_ENC_HEVC_PROFILE_FREXT_GUID; + chroma_format_index = 3; + bitdepth_minus8 = 2; + } + break; + default: + GST_ERROR_OBJECT (self, "Unexpected format %s", + gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (info))); + g_assert_not_reached (); + return FALSE; + } + + g_mutex_lock (&self->prop_lock); + + init_params->version = gst_nvenc_get_initialize_params_version (); + init_params->encodeGUID = NV_ENC_CODEC_HEVC_GUID; + + init_params->encodeWidth = GST_VIDEO_INFO_WIDTH (info); + init_params->maxEncodeWidth = GST_VIDEO_INFO_WIDTH (info); + init_params->encodeHeight = GST_VIDEO_INFO_HEIGHT (info); + init_params->maxEncodeHeight = GST_VIDEO_INFO_HEIGHT (info); + init_params->enablePTD = TRUE; + if (dev_caps->async_encoding_support) + init_params->enableEncodeAsync = 1; + if (info->fps_d > 0 && info->fps_n > 0) { + init_params->frameRateNum = info->fps_n; + init_params->frameRateDen = info->fps_d; + } else { + init_params->frameRateNum = 0; + init_params->frameRateDen = 1; + } + + init_params->enableWeightedPrediction = self->weighted_pred; + + if (gst_util_fraction_multiply (GST_VIDEO_INFO_WIDTH (info), + GST_VIDEO_INFO_HEIGHT (info), GST_VIDEO_INFO_PAR_N (info), + GST_VIDEO_INFO_PAR_D (info), &dar_n, &dar_d) && dar_n > 0 + && dar_d > 0) { + init_params->darWidth = dar_n; + init_params->darHeight = dar_d; + } + + gst_nv_encoder_preset_to_guid (self->preset, &init_params->presetGUID); + + preset_config.version = gst_nvenc_get_preset_config_version (); + preset_config.presetCfg.version = gst_nvenc_get_config_version (); + + status = NvEncGetEncodePresetConfig (session, NV_ENC_CODEC_HEVC_GUID, + init_params->presetGUID, &preset_config); + if (status != NV_ENC_SUCCESS) { + GST_ERROR_OBJECT (self, "Failed to get preset config %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + g_mutex_unlock (&self->prop_lock); + return FALSE; + } + + *config = preset_config.presetCfg; + if (self->gop_size < 0) { + config->gopLength = NVENC_INFINITE_GOPLENGTH; + config->frameIntervalP = 1; + } else if (self->gop_size > 0) { + config->gopLength = self->gop_size; + /* frameIntervalP + * 0: All Intra frames + * 1: I/P only + * 2: IBP + * 3: IBBP + */ + config->frameIntervalP = self->bframes + 1; + } else { + /* gop size == 0 means all intra frames */ + config->gopLength = 1; + config->frameIntervalP = 0; + } + + rc_params = &config->rcParams; + rc_mode = self->rc_mode; + + if (self->bitrate) + rc_params->averageBitRate = self->bitrate * 1024; + if (self->max_bitrate) + rc_params->maxBitRate = self->max_bitrate * 1024; + if (self->vbv_buffer_size) + rc_params->vbvBufferSize = self->vbv_buffer_size * 1024; + + if (rc_mode == GST_NV_ENCODER_RC_MODE_DEFAULT) { + if (self->qp_const_i >= 0) + rc_mode = GST_NV_ENCODER_RC_MODE_CONSTQP; + } + + if (self->qp_min_i >= 0) { + rc_params->enableMinQP = TRUE; + rc_params->minQP.qpIntra = self->qp_min_i; + if (self->qp_min_p >= 0) { + rc_params->minQP.qpInterP = self->qp_min_p; + } else { + rc_params->minQP.qpInterP = rc_params->minQP.qpIntra; + } + if (self->qp_min_b >= 0) { + rc_params->minQP.qpInterB = self->qp_min_b; + } else { + rc_params->minQP.qpInterB = rc_params->minQP.qpInterP; + } + } + + if (self->qp_max_i >= 0) { + rc_params->enableMaxQP = TRUE; + rc_params->maxQP.qpIntra = self->qp_max_i; + if (self->qp_max_p >= 0) { + rc_params->maxQP.qpInterP = self->qp_max_p; + } else { + rc_params->maxQP.qpInterP = rc_params->maxQP.qpIntra; + } + if (self->qp_max_b >= 0) { + rc_params->maxQP.qpInterB = self->qp_max_b; + } else { + rc_params->maxQP.qpInterB = rc_params->maxQP.qpInterP; + } + } + + if (rc_mode == GST_NV_ENCODER_RC_MODE_CONSTQP && self->qp_const_i >= 0) { + rc_params->enableMaxQP = TRUE; + rc_params->maxQP.qpIntra = self->qp_max_i; + if (self->qp_max_p >= 0) { + rc_params->maxQP.qpInterP = self->qp_max_p; + } else { + rc_params->maxQP.qpInterP = rc_params->maxQP.qpIntra; + } + if (self->qp_max_b >= 0) { + rc_params->maxQP.qpInterB = self->qp_max_b; + } else { + rc_params->maxQP.qpInterB = rc_params->maxQP.qpInterP; + } + } + + rc_params->rateControlMode = gst_nv_encoder_rc_mode_to_native (rc_mode); + + if (self->spatial_aq) { + rc_params->enableAQ = TRUE; + rc_params->aqStrength = self->aq_strength; + } + + rc_params->enableTemporalAQ = self->temporal_aq; + + if (self->rc_lookahead) { + rc_params->enableLookahead = 1; + rc_params->lookaheadDepth = self->rc_lookahead; + rc_params->disableIadapt = !self->i_adapt; + rc_params->disableBadapt = !self->b_adapt; + } + + rc_params->strictGOPTarget = self->strict_gop; + rc_params->enableNonRefP = self->non_ref_p; + rc_params->zeroReorderDelay = self->zero_latency; + + if (self->const_quality) { + guint scaled = (gint) (self->const_quality * 256.0); + + rc_params->targetQuality = (guint8) (scaled >> 8); + rc_params->targetQualityLSB = (guint8) (scaled & 0xff); + } + self->init_param_updated = FALSE; + self->bitrate_updated = FALSE; + self->rc_param_updated = FALSE; + + config->profileGUID = selected_profile; + + hevc_config = &config->encodeCodecConfig.hevcConfig; + vui = &hevc_config->hevcVUIParameters; + + hevc_config->level = NV_ENC_LEVEL_AUTOSELECT; + hevc_config->chromaFormatIDC = chroma_format_index; + hevc_config->pixelBitDepthMinus8 = bitdepth_minus8; + hevc_config->idrPeriod = config->gopLength; + hevc_config->outputAUD = self->aud; + if (self->stream_format == GST_NV_H265_ENCODER_HVC1) { + hevc_config->disableSPSPPS = 1; + hevc_config->repeatSPSPPS = 0; + } else if (self->repeat_sequence_header) { + hevc_config->disableSPSPPS = 0; + hevc_config->repeatSPSPPS = 1; + } else { + hevc_config->disableSPSPPS = 0; + hevc_config->repeatSPSPPS = 0; + } + + vui->videoSignalTypePresentFlag = 1; + /* Unspecified */ + vui->videoFormat = 5; + if (info->colorimetry.range == GST_VIDEO_COLOR_RANGE_0_255) { + vui->videoFullRangeFlag = 1; + } else { + vui->videoFullRangeFlag = 0; + } + + vui->colourDescriptionPresentFlag = 1; + vui->colourMatrix = gst_video_color_matrix_to_iso (info->colorimetry.matrix); + vui->colourPrimaries = + gst_video_color_primaries_to_iso (info->colorimetry.primaries); + vui->transferCharacteristics = + gst_video_transfer_function_to_iso (info->colorimetry.transfer); + + g_mutex_unlock (&self->prop_lock); + + return TRUE; +} + +static gboolean +gst_nv_h265_encoder_set_output_state (GstNvEncoder * encoder, + GstVideoCodecState * state, gpointer session) +{ + GstNvH265Encoder *self = GST_NV_H265_ENCODER (encoder); + GstVideoCodecState *output_state; + NV_ENC_SEQUENCE_PARAM_PAYLOAD seq_params = { 0, }; + guint8 vpsspspps[1024]; + guint32 seq_size; + GstCaps *caps; + const gchar *profile_from_vps; + NVENCSTATUS status; + std::set < std::string > downstream_profiles; + std::string caps_str; + GstTagList *tags; + GstBuffer *codec_data = NULL; + GstH265NalUnit vps_nalu, sps_nalu, pps_nalu; + GstH265ParserResult rst; + gboolean packetized = FALSE; + GstH265VPS vps; + GstH265SPS sps; + gint i, j, k = 0; + + if (self->stream_format != GST_NV_H265_ENCODER_BYTE_STREAM) + packetized = TRUE; + + caps_str = "video/x-h265, alignment = (string) au"; + + gst_nv_h265_encoder_get_downstream_profiles_and_format (self, + downstream_profiles, NULL); + + seq_params.version = gst_nvenc_get_sequence_param_payload_version (); + seq_params.inBufferSize = sizeof (vpsspspps); + seq_params.spsppsBuffer = &vpsspspps; + seq_params.outSPSPPSPayloadSize = &seq_size; + status = NvEncGetSequenceParams (session, &seq_params); + if (status != NV_ENC_SUCCESS) { + GST_ERROR_OBJECT (self, "Failed to get sequence header, status %" + GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); + return FALSE; + } + + rst = gst_h265_parser_identify_nalu (self->parser, + vpsspspps, 0, seq_size, &vps_nalu); + if (rst != GST_H265_PARSER_OK) { + GST_ERROR_OBJECT (self, "Failed to identify VPS nal"); + return FALSE; + } + + rst = gst_h265_parser_parse_vps (self->parser, &vps_nalu, &vps); + if (rst != GST_H265_PARSER_OK) { + GST_ERROR_OBJECT (self, "Failed to parse VPS"); + return FALSE; + } + + rst = gst_h265_parser_identify_nalu (self->parser, + vpsspspps, vps_nalu.offset + vps_nalu.size, seq_size, &sps_nalu); + if (rst != GST_H265_PARSER_OK && packetized) { + GST_ERROR_OBJECT (self, "Failed to identify SPS nal, %d", rst); + return FALSE; + } + + if (packetized) { + rst = gst_h265_parser_parse_sps (self->parser, &sps_nalu, &sps, TRUE); + if (rst != GST_H265_PARSER_OK) { + GST_ERROR_OBJECT (self, "Failed to parse SPS"); + return FALSE; + } + } + + rst = gst_h265_parser_identify_nalu_unchecked (self->parser, + vpsspspps, sps_nalu.offset + sps_nalu.size, seq_size, &pps_nalu); + if (rst != GST_H265_PARSER_OK && packetized) { + GST_ERROR_OBJECT (self, "Failed to identify PPS nal, %d", rst); + return FALSE; + } + + if (packetized) { + GstMapInfo info; + guint8 *data; + guint16 min_spatial_segmentation_idc = 0; + GstH265ProfileTierLevel *ptl; + + codec_data = gst_buffer_new_and_alloc (38 + + vps_nalu.size + sps_nalu.size + pps_nalu.size); + + gst_buffer_map (codec_data, &info, GST_MAP_WRITE); + data = (guint8 *) info.data; + + memset (data, 0, info.size); + + ptl = &sps.profile_tier_level; + if (sps.vui_parameters_present_flag) { + min_spatial_segmentation_idc = + sps.vui_params.min_spatial_segmentation_idc; + } + + data[0] = 1; + data[1] = + (ptl->profile_space << 5) | (ptl->tier_flag << 5) | ptl->profile_idc; + for (i = 2; i < 6; i++) { + for (j = 7; j >= 0; j--) { + data[i] |= (ptl->profile_compatibility_flag[k] << j); + k++; + } + } + + data[6] = + (ptl->progressive_source_flag << 7) | + (ptl->interlaced_source_flag << 6) | + (ptl->non_packed_constraint_flag << 5) | + (ptl->frame_only_constraint_flag << 4) | + (ptl->max_12bit_constraint_flag << 3) | + (ptl->max_10bit_constraint_flag << 2) | + (ptl->max_8bit_constraint_flag << 1) | + (ptl->max_422chroma_constraint_flag); + + data[7] = + (ptl->max_420chroma_constraint_flag << 7) | + (ptl->max_monochrome_constraint_flag << 6) | + (ptl->intra_constraint_flag << 5) | + (ptl->one_picture_only_constraint_flag << 4) | + (ptl->lower_bit_rate_constraint_flag << 3) | + (ptl->max_14bit_constraint_flag << 2); + + data[12] = ptl->level_idc; + + GST_WRITE_UINT16_BE (data + 13, min_spatial_segmentation_idc); + data[13] |= 0xf0; + data[15] = 0xfc; + data[16] = 0xfc | sps.chroma_format_idc; + data[17] = 0xf8 | sps.bit_depth_luma_minus8; + data[18] = 0xf8 | sps.bit_depth_chroma_minus8; + data[19] = 0x00; + data[20] = 0x00; + data[21] = + 0x00 | ((sps.max_sub_layers_minus1 + + 1) << 3) | (sps.temporal_id_nesting_flag << 2) | 3; + GST_WRITE_UINT8 (data + 22, 3); /* numOfArrays */ + + data += 23; + + /* vps */ + data[0] = 0x00 | 0x20; + data++; + GST_WRITE_UINT16_BE (data, 1); + data += 2; + GST_WRITE_UINT16_BE (data, vps_nalu.size); + data += 2; + memcpy (data, vps_nalu.data + vps_nalu.offset, vps_nalu.size); + data += vps_nalu.size; + + /* sps */ + data[0] = 0x00 | 0x21; + data++; + GST_WRITE_UINT16_BE (data, 1); + data += 2; + GST_WRITE_UINT16_BE (data, sps_nalu.size); + data += 2; + memcpy (data, sps_nalu.data + sps_nalu.offset, sps_nalu.size); + data += sps_nalu.size; + + /* pps */ + data[0] = 0x00 | 0x22; + data++; + GST_WRITE_UINT16_BE (data, 1); + data += 2; + GST_WRITE_UINT16_BE (data, pps_nalu.size); + data += 2; + memcpy (data, pps_nalu.data + pps_nalu.offset, pps_nalu.size); + gst_buffer_unmap (codec_data, &info); + } + + profile_from_vps = + gst_codec_utils_h265_get_profile (vps_nalu.data + vps_nalu.offset + + vps_nalu.header_bytes + 4, vps_nalu.size - vps_nalu.header_bytes - 4); + if (!profile_from_vps) { + GST_WARNING_OBJECT (self, "Failed to parse profile from SPS"); + } else if (!downstream_profiles.empty ()) { + if (downstream_profiles.find (profile_from_vps) != + downstream_profiles.end ()) { + caps_str += ", profile = (string) " + std::string (profile_from_vps); + } else if (downstream_profiles.find ("main-10") != + downstream_profiles.end () && strcmp (profile_from_vps, "main") == 0) { + caps_str += ", profile = (string) main-10"; + } else if (downstream_profiles.find ("main-444-10") != + downstream_profiles.end () && + strcmp (profile_from_vps, "main-444") == 0) { + caps_str += ", profile = (string) main-444-10"; + } + } else { + caps_str += ", profile = (string) " + std::string (profile_from_vps); + } + + switch (self->stream_format) { + case GST_NV_H265_ENCODER_HVC1: + caps_str += ", stream-format = (string) hvc1"; + break; + case GST_NV_H265_ENCODER_HEV1: + caps_str += ", stream-format = (string) hev1"; + break; + default: + caps_str += ", stream-format = (string) byte-stream"; + break; + } + + caps = gst_caps_from_string (caps_str.c_str ()); + + if (packetized) { + gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, codec_data, NULL); + gst_buffer_unref (codec_data); + } + + output_state = gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (self), + caps, state); + + GST_INFO_OBJECT (self, "Output caps: %" GST_PTR_FORMAT, output_state->caps); + gst_video_codec_state_unref (output_state); + + tags = gst_tag_list_new_empty (); + gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_ENCODER, + "nvh265encoder", NULL); + + gst_video_encoder_merge_tags (GST_VIDEO_ENCODER (encoder), + tags, GST_TAG_MERGE_REPLACE); + gst_tag_list_unref (tags); + + return TRUE; +} + +static GstBuffer * +gst_nv_h265_encoder_create_output_buffer (GstNvEncoder * + encoder, NV_ENC_LOCK_BITSTREAM * bitstream) +{ + GstNvH265Encoder *self = GST_NV_H265_ENCODER (encoder); + GstBuffer *buffer; + GstH265ParserResult rst; + GstH265NalUnit nalu; + + if (self->stream_format == GST_NV_H265_ENCODER_BYTE_STREAM) { + return gst_buffer_new_memdup (bitstream->bitstreamBufferPtr, + bitstream->bitstreamSizeInBytes); + } + + buffer = gst_buffer_new (); + rst = gst_h265_parser_identify_nalu (self->parser, + (guint8 *) bitstream->bitstreamBufferPtr, 0, + bitstream->bitstreamSizeInBytes, &nalu); + + if (rst == GST_H265_PARSER_NO_NAL_END) + rst = GST_H265_PARSER_OK; + + while (rst == GST_H265_PARSER_OK) { + GstMemory *mem; + guint8 *data; + + data = (guint8 *) g_malloc0 (nalu.size + 4); + GST_WRITE_UINT32_BE (data, nalu.size); + memcpy (data + 4, nalu.data + nalu.offset, nalu.size); + + mem = gst_memory_new_wrapped ((GstMemoryFlags) 0, data, nalu.size + 4, + 0, nalu.size + 4, data, (GDestroyNotify) g_free); + gst_buffer_append_memory (buffer, mem); + + rst = gst_h265_parser_identify_nalu (self->parser, + (guint8 *) bitstream->bitstreamBufferPtr, nalu.offset + nalu.size, + bitstream->bitstreamSizeInBytes, &nalu); + + if (rst == GST_H265_PARSER_NO_NAL_END) + rst = GST_H265_PARSER_OK; + } + + return buffer; +} + +static GstNvEncoderReconfigure +gst_nv_h265_encoder_check_reconfigure (GstNvEncoder * encoder, + NV_ENC_CONFIG * config) +{ + GstNvH265Encoder *self = GST_NV_H265_ENCODER (encoder); + GstNvEncoderReconfigure reconfig = GST_NV_ENCODER_RECONFIGURE_NONE; + + /* Dynamic RC param update is not tested, do soft-reconfigure only for + * bitrate update */ + g_mutex_lock (&self->prop_lock); + if (self->init_param_updated || self->rc_param_updated) { + reconfig = GST_NV_ENCODER_RECONFIGURE_FULL; + goto done; + } + + if (self->bitrate_updated) { + GstNvH265EncoderClass *klass = GST_NV_H265_ENCODER_GET_CLASS (self); + if (klass->dev_caps.dyn_bitrate_change > 0) { + config->rcParams.averageBitRate = self->bitrate * 1024; + config->rcParams.maxBitRate = self->max_bitrate * 1024; + reconfig = GST_NV_ENCODER_RECONFIGURE_BITRATE; + } else { + reconfig = GST_NV_ENCODER_RECONFIGURE_FULL; + } + } + +done: + self->init_param_updated = FALSE; + self->rc_param_updated = FALSE; + self->bitrate_updated = FALSE; + g_mutex_unlock (&self->prop_lock); + + return reconfig; +} + +static GstNvH265EncoderClassData * +gst_nv_h265_encoder_create_class_data (GstObject * device, gpointer session, + gboolean d3d11_mode) +{ + NVENCSTATUS status; + GstNvH265EncoderDeviceCaps dev_caps = { 0, }; + NV_ENC_CAPS_PARAM caps_param = { 0, }; + GUID profile_guids[16]; + NV_ENC_BUFFER_FORMAT input_formats[16]; + guint32 profile_guid_count = 0; + guint32 input_format_count = 0; + std::string sink_caps_str; + std::string src_caps_str; + std::string format_str; + std::set < std::string > formats; + std::set < std::string > profiles; + std::string profile_str; + std::string resolution_str; + GstNvH265EncoderClassData *cdata; + GstCaps *sink_caps; + GstCaps *system_caps; + + status = NvEncGetEncodeProfileGUIDs (session, NV_ENC_CODEC_HEVC_GUID, + profile_guids, G_N_ELEMENTS (profile_guids), &profile_guid_count); + if (status != NV_ENC_SUCCESS || profile_guid_count == 0) { + GST_WARNING_OBJECT (device, "Unable to get supported profiles"); + return NULL; + } + + status = NvEncGetInputFormats (session, NV_ENC_CODEC_HEVC_GUID, input_formats, + G_N_ELEMENTS (input_formats), &input_format_count); + if (status != NV_ENC_SUCCESS || input_format_count == 0) { + GST_WARNING_OBJECT (device, "Unable to get supported input formats"); + return NULL; + } + + 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, NV_ENC_CODEC_HEVC_GUID, &caps_param, \ + &_val); \ + if (status != NV_ENC_SUCCESS) { \ + GST_WARNING_OBJECT (device, "Unable to query %s, status: %" \ + GST_NVENC_STATUS_FORMAT, G_STRINGIFY (to_query), \ + GST_NVENC_STATUS_ARGS (status)); \ + val = default_val; \ + } else { \ + GST_DEBUG_OBJECT (device, "%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.seperate_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 + + for (guint32 i = 0; i < input_format_count; i++) { + switch (input_formats[i]) { + case NV_ENC_BUFFER_FORMAT_NV12: + formats.insert ("NV12"); + break; + case NV_ENC_BUFFER_FORMAT_YUV444: + if (!d3d11_mode && dev_caps.yuv444_encode) + formats.insert ("Y444"); + break; + case NV_ENC_BUFFER_FORMAT_YUV420_10BIT: + if (dev_caps.supports_10bit_encode) + formats.insert ("P010_10LE"); + break; + case NV_ENC_BUFFER_FORMAT_YUV444_10BIT: + if (dev_caps.supports_10bit_encode && dev_caps.yuv444_encode) + formats.insert ("Y444_16LE"); + break; + default: + break; + } + } + + if (formats.empty ()) { + GST_WARNING_OBJECT (device, "Empty supported input format"); + return NULL; + } +#define APPEND_STRING(dst,set,str) G_STMT_START { \ + if (set.find(str) != set.end()) { \ + if (!first) \ + dst += ", "; \ + dst += str; \ + first = false; \ + } \ +} G_STMT_END + + if (formats.size () == 1) { + format_str = "format = (string) " + *(formats.begin ()); + } else { + bool first = true; + + format_str = "format = (string) { "; + APPEND_STRING (format_str, formats, "NV12"); + APPEND_STRING (format_str, formats, "P010_10LE"); + APPEND_STRING (format_str, formats, "Y444"); + APPEND_STRING (format_str, formats, "Y444_16LE"); + format_str += " }"; + } + + for (guint32 i = 0; i < profile_guid_count; i++) { + if (profile_guids[i] == NV_ENC_HEVC_PROFILE_MAIN_GUID) { + profiles.insert ("main"); + } else if (profile_guids[i] == NV_ENC_HEVC_PROFILE_MAIN10_GUID) { + profiles.insert ("main-10"); + } else if (profile_guids[i] == NV_ENC_HEVC_PROFILE_FREXT_GUID) { + if (formats.find ("Y444") != formats.end ()) + profiles.insert ("main-444"); + if (formats.find ("Y444_16LE") != formats.end ()) + profiles.insert ("main-444-10"); + } + } + + if (profiles.empty ()) { + GST_WARNING_OBJECT (device, "Empty supported h265 profile"); + return NULL; + } + + if (profiles.size () == 1) { + profile_str = "profile = (string) " + *(profiles.begin ()); + } else { + bool first = true; + + profile_str = "profile = (string) { "; + APPEND_STRING (profile_str, profiles, "main"); + APPEND_STRING (profile_str, profiles, "main-10"); + APPEND_STRING (profile_str, profiles, "main-444"); + APPEND_STRING (profile_str, profiles, "main-444-10"); + profile_str += " }"; + } +#undef APPEND_STRING + + resolution_str = "width = (int) [ " + + std::to_string (GST_ROUND_UP_16 (dev_caps.width_min)) + + ", " + std::to_string (dev_caps.width_max) + " ]"; + resolution_str += ", height = (int) [ " + + std::to_string (GST_ROUND_UP_16 (dev_caps.height_min)) + + ", " + std::to_string (dev_caps.height_max) + " ]"; + + sink_caps_str = "video/x-raw, " + format_str + ", " + resolution_str + + ", interlace-mode = (string) progressive"; + + src_caps_str = "video/x-h265, " + resolution_str + ", " + profile_str + + ", stream-format = (string) { hvc1, hev1, byte-stream }" + + ", alignment = (string) au"; + + system_caps = gst_caps_from_string (sink_caps_str.c_str ()); + sink_caps = gst_caps_copy (system_caps); +#ifdef HAVE_NVCODEC_GST_D3D11 + if (d3d11_mode) { + gst_caps_set_features (sink_caps, 0, + gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY, NULL)); + } else +#endif + { + gst_caps_set_features (sink_caps, 0, + gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_CUDA_MEMORY, NULL)); + } + + gst_caps_append (sink_caps, system_caps); + + cdata = g_new0 (GstNvH265EncoderClassData, 1); + cdata->sink_caps = sink_caps; + cdata->src_caps = gst_caps_from_string (src_caps_str.c_str ()); + cdata->dev_caps = dev_caps; + cdata->d3d11_mode = d3d11_mode; + if (d3d11_mode) + g_object_get (device, "adapter-luid", &cdata->adapter_luid, NULL); + else + g_object_get (device, "cuda-device-id", &cdata->cuda_device_id, NULL); + + GST_MINI_OBJECT_FLAG_SET (cdata->sink_caps, + GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); + GST_MINI_OBJECT_FLAG_SET (cdata->src_caps, + GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); + + return cdata; +} + +void +gst_nv_h265_encoder_register_cuda (GstPlugin * plugin, GstCudaContext * context, + guint rank) +{ + NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = { 0, }; + gpointer session; + NVENCSTATUS status; + GstNvH265EncoderClassData *cdata; + + GST_DEBUG_CATEGORY_INIT (gst_nv_h265_encoder_debug, "nvh265encoder", 0, + "nvh265encoder"); + + session_params.version = + gst_nvenc_get_open_encode_session_ex_params_version (); + session_params.deviceType = NV_ENC_DEVICE_TYPE_CUDA; + session_params.device = gst_cuda_context_get_handle (context); + session_params.apiVersion = gst_nvenc_get_api_version (); + + status = NvEncOpenEncodeSessionEx (&session_params, &session); + if (status != NV_ENC_SUCCESS) { + GST_WARNING_OBJECT (context, "Failed to open session"); + return; + } + + cdata = gst_nv_h265_encoder_create_class_data (GST_OBJECT (context), session, + FALSE); + NvEncDestroyEncoder (session); + + if (!cdata) + return; + + GType type; + gchar *type_name; + gchar *feature_name; + GTypeInfo type_info = { + sizeof (GstNvH265EncoderClass), + NULL, + NULL, + (GClassInitFunc) gst_nv_h265_encoder_class_init, + NULL, + cdata, + sizeof (GstNvH265Encoder), + 0, + (GInstanceInitFunc) gst_nv_h265_encoder_init, + }; + + type_name = g_strdup ("GstNvCudaH265Enc"); + feature_name = g_strdup ("nvcudah265enc"); + + gint index = 0; + while (g_type_from_name (type_name)) { + index++; + g_free (type_name); + g_free (feature_name); + type_name = g_strdup_printf ("GstNvCudaH265Device%dEnc", index); + feature_name = g_strdup_printf ("nvcudah265device%denc", index); + } + + type = g_type_register_static (GST_TYPE_NV_ENCODER, type_name, + &type_info, (GTypeFlags) 0); + + if (rank > 0 && index != 0) + rank--; + + if (!gst_element_register (plugin, feature_name, rank, type)) + GST_WARNING ("Failed to register plugin '%s'", type_name); + + g_free (type_name); + g_free (feature_name); +} + +#ifdef HAVE_NVCODEC_GST_D3D11 +void +gst_nv_h265_encoder_register_d3d11 (GstPlugin * plugin, GstD3D11Device * device, + guint rank) +{ + NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = { 0, }; + gpointer session; + NVENCSTATUS status; + GstNvH265EncoderClassData *cdata; + + GST_DEBUG_CATEGORY_INIT (gst_nv_h265_encoder_debug, "nvh265encoder", 0, + "nvh265encoder"); + + session_params.version = + gst_nvenc_get_open_encode_session_ex_params_version (); + session_params.deviceType = NV_ENC_DEVICE_TYPE_DIRECTX; + session_params.device = gst_d3d11_device_get_device_handle (device); + session_params.apiVersion = gst_nvenc_get_api_version (); + + status = NvEncOpenEncodeSessionEx (&session_params, &session); + if (status != NV_ENC_SUCCESS) { + GST_WARNING_OBJECT (device, "Failed to open session"); + return; + } + + cdata = gst_nv_h265_encoder_create_class_data (GST_OBJECT (device), session, + TRUE); + NvEncDestroyEncoder (session); + + if (!cdata) + return; + + GType type; + gchar *type_name; + gchar *feature_name; + GTypeInfo type_info = { + sizeof (GstNvH265EncoderClass), + NULL, + NULL, + (GClassInitFunc) gst_nv_h265_encoder_class_init, + NULL, + cdata, + sizeof (GstNvH265Encoder), + 0, + (GInstanceInitFunc) gst_nv_h265_encoder_init, + }; + + type_name = g_strdup ("GstNvD3D11H265Enc"); + feature_name = g_strdup ("nvd3d11h265enc"); + + gint index = 0; + while (g_type_from_name (type_name)) { + index++; + g_free (type_name); + g_free (feature_name); + type_name = g_strdup_printf ("GstNvD3D11H265Device%dEnc", index); + feature_name = g_strdup_printf ("nvd3d11h265device%denc", index); + } + + type = g_type_register_static (GST_TYPE_NV_ENCODER, type_name, + &type_info, (GTypeFlags) 0); + + if (rank > 0 && index != 0) + rank--; + + if (!gst_element_register (plugin, feature_name, rank, type)) + GST_WARNING ("Failed to register plugin '%s'", type_name); + + g_free (type_name); + g_free (feature_name); +} +#endif diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh265encoder.h b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh265encoder.h new file mode 100644 index 0000000000..a29fc5494a --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh265encoder.h @@ -0,0 +1,36 @@ +/* GStreamer + * Copyright (C) 2022 Seungha Yang + * + * 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. + */ + +#pragma once + +#include "gstnvencoder.h" + +G_BEGIN_DECLS + +void gst_nv_h265_encoder_register_cuda (GstPlugin * plugin, + GstCudaContext * context, + guint rank); + +#ifdef HAVE_NVCODEC_GST_D3D11 +void gst_nv_h265_encoder_register_d3d11 (GstPlugin * plugin, + GstD3D11Device * device, + guint rank); +#endif + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/meson.build b/subprojects/gst-plugins-bad/sys/nvcodec/meson.build index 89bbb3b487..39d06dec0c 100644 --- a/subprojects/gst-plugins-bad/sys/nvcodec/meson.build +++ b/subprojects/gst-plugins-bad/sys/nvcodec/meson.build @@ -25,6 +25,9 @@ nvcodec_sources = [ 'gstcudascale.c', 'gstnvvp8dec.c', 'gstnvvp9dec.c', + 'gstnvencoder.cpp', + 'gstnvh264encoder.cpp', + 'gstnvh265encoder.cpp', ] nvmm_sources = [ @@ -36,14 +39,14 @@ if get_option('nvcodec').disabled() endif plugin_incdirs = [configinc, include_directories('./stub')] -extra_c_args = ['-DGST_USE_UNSTABLE_API'] +extra_args = ['-DGST_USE_UNSTABLE_API'] if gstgl_dep.found() - extra_c_args += ['-DHAVE_NVCODEC_GST_GL=1'] + extra_args += ['-DHAVE_NVCODEC_GST_GL=1'] endif if gstd3d11_dep.found() - extra_c_args += ['-DHAVE_NVCODEC_GST_D3D11=1', '-DCOBJMACROS'] + extra_args += ['-DHAVE_NVCODEC_GST_D3D11=1', '-DCOBJMACROS'] endif if host_system == 'linux' @@ -57,14 +60,29 @@ if host_system == 'linux' endif if have_nvmm - extra_c_args += ['-DHAVE_NVCODEC_NVMM'] + extra_args += ['-DHAVE_NVCODEC_NVMM'] nvcodec_sources += nvmm_sources endif endif +override_opt = [] +if host_system == 'windows' + # MinGW 32bits compiler seems to be complaining about redundant-decls + # when ComPtr is in use. Let's just disable the warning + if cc.get_id() != 'msvc' + extra_args += cc.get_supported_arguments([ + '-Wno-redundant-decls', + ]) + endif +else + override_opt += ['cpp_std=c++11'] +endif + gstnvcodec = library('gstnvcodec', nvcodec_sources, - c_args : gst_plugins_bad_args + extra_c_args, + c_args : gst_plugins_bad_args + extra_args, + cpp_args : gst_plugins_bad_args + extra_args, + override_options: override_opt, include_directories : plugin_incdirs, dependencies : [gstbase_dep, gstvideo_dep, gstpbutils_dep, gstgl_dep, gstglproto_dep, gmodule_dep, gstcodecs_dep, gstd3d11_dep], install : true, diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/plugin.c b/subprojects/gst-plugins-bad/sys/nvcodec/plugin.c index ea321bb75f..2df7529624 100644 --- a/subprojects/gst-plugins-bad/sys/nvcodec/plugin.c +++ b/subprojects/gst-plugins-bad/sys/nvcodec/plugin.c @@ -43,6 +43,12 @@ #include "gstcudanvmm.h" #endif +#ifdef HAVE_NVCODEC_GST_D3D11 +#include +#endif +#include "gstnvh264encoder.h" +#include "gstnvh265encoder.h" + GST_DEBUG_CATEGORY (gst_nvcodec_debug); GST_DEBUG_CATEGORY (gst_nvdec_debug); GST_DEBUG_CATEGORY (gst_nvenc_debug); @@ -59,7 +65,7 @@ plugin_init (GstPlugin * plugin) { CUresult cuda_ret; gint dev_count = 0; - gint i; + guint i; gboolean nvdec_available = TRUE; gboolean nvenc_available = TRUE; /* hardcoded minimum supported version */ @@ -139,24 +145,15 @@ plugin_init (GstPlugin * plugin) } for (i = 0; i < dev_count; i++) { - CUdevice cuda_device; + GstCudaContext *context = gst_cuda_context_new (i); CUcontext cuda_ctx; - cuda_ret = CuDeviceGet (&cuda_device, i); - if (cuda_ret != CUDA_SUCCESS) { - GST_WARNING ("Failed to get device handle %d, ret: 0x%x", i, - (gint) cuda_ret); + if (!context) { + GST_WARNING ("Failed to create context for deevice %d", i); continue; } - cuda_ret = CuCtxCreate (&cuda_ctx, 0, cuda_device); - if (cuda_ret != CUDA_SUCCESS) { - GST_WARNING ("Failed to create cuda context, ret: 0x%x", (gint) cuda_ret); - continue; - } - - CuCtxPopCurrent (NULL); - + cuda_ctx = gst_cuda_context_get_handle (context); if (nvdec_available) { gint j; @@ -237,10 +234,32 @@ plugin_init (GstPlugin * plugin) } } - if (nvenc_available) - gst_nvenc_plugin_init (plugin, i, cuda_ctx); + if (nvenc_available) { +#ifdef HAVE_NVCODEC_GST_D3D11 + if (g_win32_check_windows_version (6, 0, 0, G_WIN32_OS_ANY)) { + gint64 adapter_luid; + GstD3D11Device *d3d11_device; - CuCtxDestroy (cuda_ctx); + g_object_get (context, "dxgi-adapter-luid", &adapter_luid, NULL); + d3d11_device = gst_d3d11_device_new_for_adapter_luid (adapter_luid, + D3D11_CREATE_DEVICE_BGRA_SUPPORT); + if (!d3d11_device) { + GST_WARNING ("Failed to d3d11 create device"); + } else { + gst_nv_h264_encoder_register_d3d11 (plugin, + d3d11_device, GST_RANK_NONE); + gst_nv_h265_encoder_register_d3d11 (plugin, + d3d11_device, GST_RANK_NONE); + gst_object_unref (d3d11_device); + } + } +#endif + gst_nv_h264_encoder_register_cuda (plugin, context, GST_RANK_NONE); + gst_nv_h265_encoder_register_cuda (plugin, context, GST_RANK_NONE); + gst_nvenc_plugin_init (plugin, i, cuda_ctx); + } + + gst_object_unref (context); } gst_cuda_memory_copy_register (plugin, GST_RANK_NONE); diff --git a/subprojects/gst-plugins-bad/tests/examples/nvcodec/nvcodec-kb.c b/subprojects/gst-plugins-bad/tests/examples/nvcodec/nvcodec-kb.c index a4dc97d150..fc82f62dd3 100644 --- a/subprojects/gst-plugins-bad/tests/examples/nvcodec/nvcodec-kb.c +++ b/subprojects/gst-plugins-bad/tests/examples/nvcodec/nvcodec-kb.c @@ -34,16 +34,20 @@ #include #endif +#ifdef G_OS_WIN32 +#include +#include +#endif + #include /* This is all not thread-safe, but doesn't have to be really */ - -#ifdef G_OS_UNIX - -static struct termios term_settings; -static gboolean term_settings_saved = FALSE; static GstNvCodecPlayKbFunc kb_callback; static gpointer kb_callback_data; + +#ifdef G_OS_UNIX +static struct termios term_settings; +static gboolean term_settings_saved = FALSE; static gulong io_watch_id; static gboolean @@ -129,7 +133,158 @@ gst_nvcodec_kb_set_key_handler (GstNvCodecPlayKbFunc kb_func, return TRUE; } -#else /* !G_OS_UNIX */ +#elif defined(G_OS_WIN32) + +typedef struct +{ + GThread *thread; + HANDLE event_handle; + HANDLE console_handle; + gboolean closing; + GMutex lock; +} Win32KeyHandler; + +static Win32KeyHandler *win32_handler = NULL; + +static gboolean +gst_nvcodec_kb_source_cb (Win32KeyHandler * handler) +{ + HANDLE h_input = handler->console_handle; + INPUT_RECORD buffer; + DWORD n; + + if (PeekConsoleInput (h_input, &buffer, 1, &n) && n == 1) { + ReadConsoleInput (h_input, &buffer, 1, &n); + + if (buffer.EventType == KEY_EVENT && buffer.Event.KeyEvent.bKeyDown) { + gchar key_val[2] = { 0 }; + + switch (buffer.Event.KeyEvent.wVirtualKeyCode) { + case VK_RIGHT: + kb_callback (GST_NVCODEC_KB_ARROW_RIGHT, kb_callback_data); + break; + case VK_LEFT: + kb_callback (GST_NVCODEC_KB_ARROW_LEFT, kb_callback_data); + break; + case VK_UP: + kb_callback (GST_NVCODEC_KB_ARROW_UP, kb_callback_data); + break; + case VK_DOWN: + kb_callback (GST_NVCODEC_KB_ARROW_DOWN, kb_callback_data); + break; + default: + key_val[0] = buffer.Event.KeyEvent.uChar.AsciiChar; + kb_callback (key_val, kb_callback_data); + break; + } + } + } + + return G_SOURCE_REMOVE; +} + +static gpointer +gst_nvcodec_kb_win32_thread (gpointer user_data) +{ + Win32KeyHandler *handler = (Win32KeyHandler *) user_data; + HANDLE handles[2]; + + handles[0] = handler->event_handle; + handles[1] = handler->console_handle; + + if (!kb_callback) + return NULL; + + while (TRUE) { + DWORD ret = WaitForMultipleObjects (2, handles, FALSE, INFINITE); + + if (ret == WAIT_FAILED) { + GST_WARNING ("WaitForMultipleObject Failed"); + return NULL; + } + + g_mutex_lock (&handler->lock); + if (handler->closing) { + g_mutex_unlock (&handler->lock); + + return NULL; + } + g_mutex_unlock (&handler->lock); + + g_idle_add ((GSourceFunc) gst_nvcodec_kb_source_cb, handler); + } + + return NULL; +} + +gboolean +gst_nvcodec_kb_set_key_handler (GstNvCodecPlayKbFunc kb_func, + gpointer user_data) +{ + gint fd = _fileno (stdin); + + if (!_isatty (fd)) { + GST_INFO ("stdin is not connected to a terminal"); + return FALSE; + } + + if (win32_handler) { + g_mutex_lock (&win32_handler->lock); + win32_handler->closing = TRUE; + g_mutex_unlock (&win32_handler->lock); + + SetEvent (win32_handler->event_handle); + g_thread_join (win32_handler->thread); + CloseHandle (win32_handler->event_handle); + + g_mutex_clear (&win32_handler->lock); + g_free (win32_handler); + win32_handler = NULL; + } + + if (kb_func) { + SECURITY_ATTRIBUTES sec_attrs; + + sec_attrs.nLength = sizeof (SECURITY_ATTRIBUTES); + sec_attrs.lpSecurityDescriptor = NULL; + sec_attrs.bInheritHandle = FALSE; + + win32_handler = g_new0 (Win32KeyHandler, 1); + + /* create cancellable event handle */ + win32_handler->event_handle = CreateEvent (&sec_attrs, TRUE, FALSE, NULL); + + if (!win32_handler->event_handle) { + GST_WARNING ("Couldn't create event handle"); + g_free (win32_handler); + win32_handler = NULL; + + return FALSE; + } + + win32_handler->console_handle = GetStdHandle (STD_INPUT_HANDLE); + if (!win32_handler->console_handle) { + GST_WARNING ("Couldn't get console handle"); + CloseHandle (win32_handler->event_handle); + g_free (win32_handler); + win32_handler = NULL; + + return FALSE; + } + + g_mutex_init (&win32_handler->lock); + win32_handler->thread = + g_thread_new ("gst-play-kb", gst_nvcodec_kb_win32_thread, + win32_handler); + } + + kb_callback = kb_func; + kb_callback_data = user_data; + + return TRUE; +} + +#else gboolean gst_nvcodec_kb_set_key_handler (GstNvCodecPlayKbFunc key_func, diff --git a/subprojects/gst-plugins-bad/tests/examples/nvcodec/nvcodec.c b/subprojects/gst-plugins-bad/tests/examples/nvcodec/nvcodec.c index 31064a7171..fd3bac1371 100644 --- a/subprojects/gst-plugins-bad/tests/examples/nvcodec/nvcodec.c +++ b/subprojects/gst-plugins-bad/tests/examples/nvcodec/nvcodec.c @@ -231,14 +231,15 @@ bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data) } static gboolean -check_nvcodec_available (void) +check_nvcodec_available (const gchar * encoder_name) { gboolean ret = TRUE; GstElement *elem; - elem = gst_element_factory_make ("nvh264enc", NULL); + elem = gst_element_factory_make (encoder_name, NULL); if (!elem) { - GST_WARNING ("nvh264enc is not available, possibly driver load failure"); + GST_WARNING ("%s is not available, possibly driver load failure", + encoder_name); return FALSE; } @@ -332,13 +333,16 @@ main (gint argc, gchar ** argv) GstCaps *caps; TestCallbackData data = { 0, }; GstPad *pad; - + gchar *encoder_name = NULL; + /* *INDENT-OFF* */ GOptionEntry options[] = { {"use-gl", 0, 0, G_OPTION_ARG_NONE, &use_gl, - "Use OpenGL memory as input to the nvenc", NULL} - , + "Use OpenGL memory as input to the nvenc", NULL}, + {"encoder", 0, 0, G_OPTION_ARG_STRING, &encoder_name, + "NVENC encoder element to test, default: nvh264enc"}, {NULL} }; + /* *INDENT-ON* */ option_ctx = g_option_context_new ("nvcodec dynamic reconfigure example"); g_option_context_add_main_entries (option_ctx, options, NULL); @@ -352,7 +356,10 @@ main (gint argc, gchar ** argv) g_option_context_free (option_ctx); gst_init (NULL, NULL); - if (!check_nvcodec_available ()) { + if (!encoder_name) + encoder_name = g_strdup ("nvh264enc"); + + if (!check_nvcodec_available (encoder_name)) { g_printerr ("Cannot load nvcodec plugin"); exit (1); } @@ -404,7 +411,7 @@ main (gint argc, gchar ** argv) capsfilter = gst_element_factory_make ("capsfilter", NULL); queue = gst_element_factory_make ("queue", NULL); - enc = gst_element_factory_make ("nvh264enc", NULL); + enc = gst_element_factory_make (encoder_name, NULL); parse = gst_element_factory_make ("h264parse", NULL); /* vbr with target bitrate */ @@ -471,6 +478,7 @@ terminate: gst_object_unref (pipeline); g_main_loop_unref (loop); + g_free (encoder_name); return exitcode; }