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