From eb0fca4180add2508e116a921e9675956a222dde Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Sun, 5 Feb 2023 23:12:47 +0900 Subject: [PATCH] nvencoder: Reuse input resource Call input resource map functions (i.e., nvEncRegisterResource, nvEncUnregisterResource, nvEncMapInputResource, and nvEncUnmapInputResource) only once and reuse the mapped resources, instead of per input frame map/unmap Part-of: --- .../sys/nvcodec/gstnvencobject.cpp | 1200 +++++++++++++++++ .../sys/nvcodec/gstnvencobject.h | 247 ++++ .../sys/nvcodec/gstnvencoder.cpp | 987 ++++---------- .../sys/nvcodec/gstnvencoder.h | 35 +- .../sys/nvcodec/gstnvh264encoder.cpp | 10 +- .../sys/nvcodec/gstnvh265encoder.cpp | 10 +- .../gst-plugins-bad/sys/nvcodec/meson.build | 2 + 7 files changed, 1700 insertions(+), 791 deletions(-) create mode 100644 subprojects/gst-plugins-bad/sys/nvcodec/gstnvencobject.cpp create mode 100644 subprojects/gst-plugins-bad/sys/nvcodec/gstnvencobject.h diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencobject.cpp b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencobject.cpp new file mode 100644 index 0000000000..576901c226 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencobject.cpp @@ -0,0 +1,1200 @@ +/* GStreamer + * Copyright (C) 2023 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 "gstnvencobject.h" +#include + +GST_DEBUG_CATEGORY_EXTERN (gst_nv_encoder_debug); +#define GST_CAT_DEFAULT gst_nv_encoder_debug + +/* Both CUDA and D3D11 use the same value */ +#define GST_MAP_NVENC (GST_MAP_FLAG_LAST << 1) +#define GST_MAP_READ_NVENC (GstMapFlags)(GST_MAP_READ | GST_MAP_NVENC) + +/* *INDENT-OFF* */ +static GstNvEncBuffer * gst_nv_enc_buffer_new (const std::string & id, + guint seq_num); +static GstNvEncResource * gst_nv_enc_resource_new (const std::string & id, + guint seq_num); +static GstNvEncTask * gst_nv_enc_task_new (const std::string & id, + guint seq_num); + +struct GstNvEncBuffer : public GstMiniObject +{ + GstNvEncBuffer (const std::string parent_id, guint seq) + : id (parent_id), seq_num (seq) + { + memset (&buffer, 0, sizeof (NV_ENC_CREATE_INPUT_BUFFER)); + memset (&buffer_lock, 0, sizeof (NV_ENC_LOCK_INPUT_BUFFER)); + + buffer.version = gst_nvenc_get_create_input_buffer_version (); + buffer_lock.version = gst_nvenc_get_lock_input_buffer_version (); + } + + std::shared_ptr object; + + NV_ENC_CREATE_INPUT_BUFFER buffer; + NV_ENC_LOCK_INPUT_BUFFER buffer_lock; + + bool locked = false; + std::string id; + guint seq_num; +}; + +GST_DEFINE_MINI_OBJECT_TYPE (GstNvEncBuffer, gst_nv_enc_buffer); + +struct GstNvEncResource : public GstMiniObject +{ + GstNvEncResource (const std::string & parent_id, guint seq) + : id (parent_id), seq_num (seq) + { + memset (&resource, 0, sizeof (NV_ENC_REGISTER_RESOURCE)); + memset (&mapped_resource, 0, sizeof (NV_ENC_MAP_INPUT_RESOURCE)); + + resource.version = gst_nvenc_get_register_resource_version (); + mapped_resource.version = gst_nvenc_get_map_input_resource_version (); + } + + std::weak_ptr object; + + NV_ENC_REGISTER_RESOURCE resource; + NV_ENC_MAP_INPUT_RESOURCE mapped_resource; + + std::string id; + guint seq_num; +}; + +GST_DEFINE_MINI_OBJECT_TYPE (GstNvEncResource, gst_nv_enc_resource); + +struct GstNvEncTask : public GstMiniObject +{ + GstNvEncTask (const std::string & parent_id, guint seq) + : id (parent_id), seq_num (seq) + { + memset (&event_params, 0, sizeof (NV_ENC_EVENT_PARAMS)); + memset (&bitstream, 0, sizeof (NV_ENC_LOCK_BITSTREAM)); + + event_params.version = gst_nvenc_get_event_params_version (); + bitstream.version = gst_nvenc_get_lock_bitstream_version (); + } + + std::shared_ptr object; + + GstNvEncBuffer *buffer = nullptr; + GstNvEncResource *resource = nullptr; + + GstBuffer *gst_buffer = nullptr;; + GstMapInfo info; + NV_ENC_DEVICE_TYPE device_type = NV_ENC_DEVICE_TYPE_CUDA; + + NV_ENC_EVENT_PARAMS event_params; + NV_ENC_OUTPUT_PTR output_ptr = nullptr; + + NV_ENC_LOCK_BITSTREAM bitstream; + bool locked = false; + std::string id; + guint seq_num; +}; + +GST_DEFINE_MINI_OBJECT_TYPE (GstNvEncTask, gst_nv_enc_task); + +bool +GstNvEncObject::IsSuccess (NVENCSTATUS status, GstNvEncObject * 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); + + if (self) { + gst_debug_log_id (GST_CAT_DEFAULT, GST_LEVEL_ERROR, file, function, + line, self->id_.c_str (), "NvEnc API call failed: 0x%x, %s", + (guint) status, status_str); + } else { + gst_debug_log (GST_CAT_DEFAULT, GST_LEVEL_ERROR, file, function, + line, nullptr, "NvEnc API call failed: 0x%x, %s", + (guint) status, status_str); + } +#endif + + return false; +} + +#define NVENC_IS_SUCCESS(status,obj) \ + GstNvEncObject::IsSuccess (status, obj, __FILE__, GST_FUNCTION, __LINE__) + +std::shared_ptr +GstNvEncObject::CreateInstance (GstElement * client, GstObject * device, + NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS * params) +{ + NVENCSTATUS status; + gpointer session; + + status = NvEncOpenEncodeSessionEx (params, &session); + if (!NVENC_IS_SUCCESS (status, nullptr)) { + GST_ERROR_OBJECT (device, "NvEncOpenEncodeSessionEx failed"); + return nullptr; + } + + std::shared_ptr self = + std::make_shared (); + self->id_ = GST_ELEMENT_NAME (client); + self->session_ = session; + +#ifdef G_OS_WIN32 + if (params->deviceType == NV_ENC_DEVICE_TYPE_DIRECTX) { + self->device_ = (GstD3D11Device *) gst_object_ref (device); + self->user_token_ = gst_d3d11_create_user_token (); + } else +#endif + { + self->context_ = (GstCudaContext *) gst_object_ref (device); + self->user_token_ = gst_cuda_create_user_token (); + } + + self->device_type_ = params->deviceType; + self->buffer_seq_ = 0; + self->resource_seq_ = 0; + self->task_seq_ = 0; + + GST_INFO_ID (self->id_.c_str (), "New encoder object for type %d is created", + self->device_type_); + + return self; +} + +GstNvEncObject::~GstNvEncObject () +{ + GST_INFO_ID (id_.c_str (), "Destroying instance"); + + DeviceLock (); + while (!buffer_queue_.empty ()) { + GstNvEncBuffer *buf = buffer_queue_.front (); + + NvEncDestroyInputBuffer (session_, buf->buffer.inputBuffer); + gst_nv_enc_buffer_unref (buf); + buffer_queue_.pop (); + } + + if (!resource_queue_.empty ()) { + GST_INFO_ID (id_.c_str (), "Have %u outstanding input resource(s)", + (guint) resource_queue_.size ()); + for (auto it : resource_queue_) + releaseResourceUnlocked (it); + } + + while (!empty_task_queue_.empty ()) { + GstNvEncTask *task = empty_task_queue_.front (); + + releaseTaskUnlocked (task); + empty_task_queue_.pop (); + } + + NvEncDestroyEncoder (session_); + DeviceUnlock (); + + gst_clear_object (&context_); + gst_clear_cuda_stream (&stream_); +#ifdef G_OS_WIN32 + gst_clear_object (&device_); +#endif + + GST_INFO_ID (id_.c_str (), "Cleared all resources"); +} + +gpointer +GstNvEncObject::GetHandle () +{ + return session_; +} + +guint +GstNvEncObject::GetTaskSize () +{ + return task_size_; +} + +void +GstNvEncObject::releaseTaskUnlocked (GstNvEncTask * task) +{ + if (!task) + return; + + if (task->output_ptr) { + NvEncDestroyBitstreamBuffer (session_, task->output_ptr); + task->output_ptr = nullptr; + } + +#ifdef G_OS_WIN32 + if (task->event_params.completionEvent) { + gpointer handle = task->event_params.completionEvent; + NvEncUnregisterAsyncEvent (session_, &task->event_params); + CloseHandle (handle); + + memset (&task->event_params, 0, sizeof (NV_ENC_EVENT_PARAMS)); + } +#endif + + gst_nv_enc_task_unref (task); +} + +NVENCSTATUS +GstNvEncObject::InitSession (NV_ENC_INITIALIZE_PARAMS * params, + GstCudaStream * stream, const GstVideoInfo * info, guint task_size) +{ + NVENCSTATUS status = NV_ENC_SUCCESS; + + if (initialized_) { + GST_ERROR_ID (id_.c_str(), "Was initialized"); + return NV_ENC_ERR_INVALID_CALL; + } + + info_ = *info; + switch (GST_VIDEO_INFO_FORMAT (info)) { + case GST_VIDEO_FORMAT_NV12: + buffer_format_ = NV_ENC_BUFFER_FORMAT_NV12; + break; + case GST_VIDEO_FORMAT_Y444: + buffer_format_ = NV_ENC_BUFFER_FORMAT_YUV444; + break; + case GST_VIDEO_FORMAT_P010_10LE: + buffer_format_ = NV_ENC_BUFFER_FORMAT_YUV420_10BIT; + break; + case GST_VIDEO_FORMAT_Y444_16LE: + buffer_format_ = NV_ENC_BUFFER_FORMAT_YUV444_10BIT; + break; + default: + GST_ERROR_ID (id_.c_str (), "Unexpected format %s", + gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (info))); + return NV_ENC_ERR_INVALID_PARAM; + } + + GST_DEBUG_ID (id_.c_str (), "Initializing encoder, buffer type %d", + buffer_format_); + + status = NvEncInitializeEncoder (session_, params); + if (!NVENC_IS_SUCCESS (status, this)) + return status; + + if (device_type_ == NV_ENC_DEVICE_TYPE_CUDA && stream) { + CUstream stream_handle; + + stream_ = gst_cuda_stream_ref (stream); + stream_handle = gst_cuda_stream_get_handle (stream); + status = NvEncSetIOCudaStreams (session_, + (NV_ENC_CUSTREAM_PTR) & stream_handle, + (NV_ENC_CUSTREAM_PTR) & stream_handle); + if (!NVENC_IS_SUCCESS (status, this)) + return status; + } + + for (guint i = 0; i < task_size; i++) { + GstNvEncTask *task = gst_nv_enc_task_new (id_, task_seq_.fetch_add (1)); + NV_ENC_CREATE_BITSTREAM_BUFFER buffer_params = { 0, }; + + task->device_type = device_type_; + + buffer_params.version = gst_nvenc_get_create_bitstream_buffer_version (); + status = NvEncCreateBitstreamBuffer (session_, &buffer_params); + if (!NVENC_IS_SUCCESS (status, this)) { + gst_nv_enc_task_unref (task); + goto out; + } + + task->output_ptr = buffer_params.bitstreamBuffer; + +#ifdef G_OS_WIN32 + if (params->enableEncodeAsync) { + task->event_params.version = gst_nvenc_get_event_params_version (); + task->event_params.completionEvent = CreateEvent (nullptr, + FALSE, FALSE, nullptr); + status = NvEncRegisterAsyncEvent (session_, &task->event_params); + if (!NVENC_IS_SUCCESS (status, this)) { + CloseHandle (task->event_params.completionEvent); + releaseTaskUnlocked (task); + goto out; + } + } +#endif + + empty_task_queue_.push (task); + } + + task_size_ = task_size; + initialized_ = true; + +out: + if (status != NV_ENC_SUCCESS) { + while (!empty_task_queue_.empty ()) { + GstNvEncTask *task = empty_task_queue_.front (); + + releaseTaskUnlocked (task); + empty_task_queue_.pop (); + } + } + + return status; +} + +NVENCSTATUS +GstNvEncObject::Reconfigure (NV_ENC_RECONFIGURE_PARAMS * params) +{ + return NvEncReconfigureEncoder (session_, params); +} + +void +GstNvEncObject::SetFlushing (bool flushing) +{ + std::lock_guard lk (lock_); + flushing_ = flushing; + cond_.notify_all (); +} + +NVENCSTATUS +GstNvEncObject::Encode (GstVideoCodecFrame * codec_frame, + NV_ENC_PIC_STRUCT pic_struct, GstNvEncTask * task) +{ + NVENCSTATUS status; + guint retry_count = 0; + const guint retry_threshold = 100; + NV_ENC_PIC_PARAMS params = { 0, }; + + std::unique_lock lk (lock_); + + params.version = gst_nvenc_get_pic_params_version (); + params.completionEvent = task->event_params.completionEvent; + + g_assert (task->buffer || task->resource); + + GST_LOG_ID (id_.c_str (), "Encoding frame %u", + codec_frame->system_frame_number); + + if (task->buffer) { + params.inputWidth = task->buffer->buffer.width; + params.inputHeight = task->buffer->buffer.height; + params.inputPitch = task->buffer->buffer_lock.pitch; + params.inputBuffer = task->buffer->buffer.inputBuffer; + params.bufferFmt = task->buffer->buffer.bufferFmt; + } else { + params.inputWidth = task->resource->resource.width; + params.inputHeight = task->resource->resource.height; + params.inputPitch = task->resource->resource.pitch; + params.inputBuffer = task->resource->mapped_resource.mappedResource; + params.bufferFmt = task->resource->mapped_resource.mappedBufferFmt; + } + + params.frameIdx = codec_frame->system_frame_number; + params.inputTimeStamp = codec_frame->pts; + params.inputDuration = codec_frame->duration; + params.outputBitstream = task->output_ptr; + params.pictureStruct = pic_struct; + + if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (codec_frame)) + params.encodePicFlags = NV_ENC_PIC_FLAG_FORCEIDR; + + do { + DeviceLock (); + status = NvEncEncodePicture (session_, ¶ms); + DeviceUnlock (); + + if (status == NV_ENC_ERR_ENCODER_BUSY) { + if (retry_count < 100) { + GST_DEBUG_ID (id_.c_str (), "GPU is busy, retry count (%d/%d)", + retry_count, retry_threshold); + retry_count++; + + /* Magic number 1ms */ + g_usleep (1000); + continue; + } else { + GST_ERROR_ID (id_.c_str (), "GPU is keep busy, give up"); + break; + } + } + + break; + } while (true); + + if (status != NV_ENC_SUCCESS && status != NV_ENC_ERR_NEED_MORE_INPUT) { + NVENC_IS_SUCCESS (status, this); + lk.unlock (); + gst_nv_enc_task_unref (task); + return status; + } + + gst_video_codec_frame_set_user_data (codec_frame, task, nullptr); + + { + std::lock_guard rlk (resource_lock_); + + if (task->resource) + active_resource_queue_.insert (task->resource); + } + + task_queue_.push (task); + cond_.notify_all (); + + return NV_ENC_SUCCESS; +} + +NVENCSTATUS +GstNvEncObject::Drain (GstNvEncTask * task) +{ + NVENCSTATUS status; + guint retry_count = 0; + const guint retry_threshold = 100; + NV_ENC_PIC_PARAMS params = { 0, }; + + std::unique_lock lk (lock_); + + params.version = gst_nvenc_get_pic_params_version (); + params.completionEvent = task->event_params.completionEvent; + params.encodePicFlags = NV_ENC_PIC_FLAG_EOS; + + do { + status = NvEncEncodePicture (session_, ¶ms); + + if (status == NV_ENC_ERR_ENCODER_BUSY) { + if (retry_count < 100) { + GST_DEBUG_ID (id_.c_str (), "GPU is busy, retry count (%d/%d)", + retry_count, retry_threshold); + retry_count++; + + /* Magic number 1ms */ + g_usleep (1000); + continue; + } else { + GST_ERROR_ID (id_.c_str (), "GPU is keep busy, give up"); + break; + } + } + + break; + } while (true); + + task_queue_.push (task); + cond_.notify_all (); + + return status; +} + +GstFlowReturn +GstNvEncObject::GetOutput (GstNvEncTask ** task) +{ + GstNvEncTask *ret = nullptr; + std::unique_lock lk (lock_); + + while (task_queue_.empty ()) + cond_.wait (lk); + + ret = task_queue_.front (); + task_queue_.pop (); + lk.unlock (); + + if (!ret->buffer && !ret->resource) { + gst_nv_enc_task_unref (ret); + return GST_FLOW_EOS; + } + +#ifdef G_OS_WIN32 + if (ret->event_params.completionEvent && + WaitForSingleObject (ret->event_params.completionEvent, 20000) == + WAIT_FAILED) { + GST_ERROR_ID (id_.c_str (), "Failed to wait for completion event"); + gst_nv_enc_task_unref (ret); + return GST_FLOW_ERROR; + } +#endif + + *task = ret; + + return GST_FLOW_OK; +} + +NVENCSTATUS +GstNvEncObject::LockBitstream (NV_ENC_LOCK_BITSTREAM * bitstream) +{ + return NvEncLockBitstream (session_, bitstream); +} + +NVENCSTATUS +GstNvEncObject::UnlockBitstream (NV_ENC_OUTPUT_PTR output_ptr) +{ + return NvEncUnlockBitstream (session_, output_ptr); +} + +NVENCSTATUS +GstNvEncObject::AcquireBuffer (GstNvEncBuffer ** buffer) +{ + GstNvEncBuffer *new_buf = nullptr; + std::unique_lock lk (lock_); + + if (buffer_queue_.empty ()) { + NVENCSTATUS status; + NV_ENC_CREATE_INPUT_BUFFER in_buf = { 0, }; + + GST_LOG_ID (id_.c_str (), "No available input buffer, creating new one"); + + in_buf.version = gst_nvenc_get_create_input_buffer_version (); + in_buf.width = info_.width; + in_buf.height = info_.height; + in_buf.bufferFmt = buffer_format_; + + status = NvEncCreateInputBuffer (session_, &in_buf); + if (!NVENC_IS_SUCCESS (status, this)) + return status; + + new_buf = gst_nv_enc_buffer_new (id_, buffer_seq_.fetch_add (1)); + new_buf->buffer = in_buf; + new_buf->buffer_lock.inputBuffer = in_buf.inputBuffer; + } else { + new_buf = buffer_queue_.front (); + buffer_queue_.pop (); + } + + g_assert (!new_buf->object); + + new_buf->object = shared_from_this (); + + *buffer = new_buf; + + GST_TRACE_ID (id_.c_str (), "Acquired buffer %u", new_buf->seq_num); + + return NV_ENC_SUCCESS; +} + +void +GstNvEncObject::runResourceGC () +{ + std::lock_guard lk (resource_lock_); + + /* hard coded max size 64 */ + if (resource_queue_.size () < 64) + return; + + GST_LOG_ID (id_.c_str (), "Running resource GC"); + + DeviceLock (); + for (auto it : resource_queue_) { + if (active_resource_queue_.find (it) == active_resource_queue_.end ()) { + releaseResourceUnlocked (it); + resource_queue_.erase (it); + } + } + DeviceUnlock (); + + GST_LOG_ID (id_.c_str (), "resource queue size after GC %u", + (guint) resource_queue_.size ()); +} + +bool +GstNvEncObject::DeviceLock () +{ + if (context_) + return gst_cuda_context_push (context_); + + return true; +} + +bool +GstNvEncObject::DeviceUnlock () +{ + if (context_) + return gst_cuda_context_pop (nullptr); + + return true; +} + +NVENCSTATUS +GstNvEncObject::acquireResourceCuda (GstMemory * mem, + GstNvEncResource ** resource) +{ + GstNvEncResource *res; + GstCudaMemory *cmem; + NV_ENC_REGISTER_RESOURCE new_resource; + NV_ENC_MAP_INPUT_RESOURCE mapped_resource; + NVENCSTATUS status; + GstMapInfo info; + + if (!gst_is_cuda_memory (mem)) { + GST_ERROR_ID (id_.c_str (), "Not a CUDA memory"); + return NV_ENC_ERR_INVALID_CALL; + } + + cmem = GST_CUDA_MEMORY_CAST (mem); + + res = (GstNvEncResource *) gst_cuda_memory_get_token_data (cmem, + user_token_); + if (res) { + auto iter = resource_queue_.find (res); + /* This resource can be released already */ + if (iter != resource_queue_.end ()) { + GST_LOG_ID (id_.c_str (), "Memory is holding registered resource"); + *resource = gst_nv_enc_resource_ref (res); + return NV_ENC_SUCCESS; + } + } + + if (!gst_memory_map (mem, &info, GST_MAP_READ_NVENC)) { + GST_ERROR_ID (id_.c_str (), "Couldn't map CUDA memory"); + return NV_ENC_ERR_MAP_FAILED; + } + + memset (&new_resource, 0, sizeof (NV_ENC_REGISTER_RESOURCE)); + memset (&mapped_resource, 0, sizeof (NV_ENC_MAP_INPUT_RESOURCE)); + + new_resource.version = gst_nvenc_get_register_resource_version (); + new_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR; + new_resource.width = cmem->info.width; + new_resource.height = cmem->info.height; + new_resource.pitch = cmem->info.stride[0]; + new_resource.resourceToRegister = info.data; + new_resource.bufferFormat = buffer_format_; + + GST_LOG_ID (id_.c_str (), "Registering CUDA resource %p, %dx%d, pitch %u", + info.data, new_resource.width, new_resource.height, new_resource.pitch); + + status = NvEncRegisterResource (session_, &new_resource); + gst_memory_unmap (mem, &info); + + if (!NVENC_IS_SUCCESS (status, this)) + return status; + + mapped_resource.version = gst_nvenc_get_map_input_resource_version (); + mapped_resource.registeredResource = new_resource.registeredResource; + status = NvEncMapInputResource (session_, &mapped_resource); + if (!NVENC_IS_SUCCESS (status, this)) { + NvEncUnregisterResource (session_, new_resource.registeredResource); + return status; + } + + res = gst_nv_enc_resource_new (id_, resource_seq_.fetch_add (1)); + /* weak ref */ + res->object = shared_from_this (); + + res->resource = new_resource; + res->mapped_resource = mapped_resource; + + gst_cuda_memory_set_token_data (cmem, user_token_, + gst_nv_enc_resource_ref (res), + (GDestroyNotify) gst_nv_enc_resource_unref); + resource_queue_.insert (res); + + *resource = res; + + return NV_ENC_SUCCESS; +} + +#ifdef G_OS_WIN32 +NVENCSTATUS +GstNvEncObject::acquireResourceD3D11 (GstMemory * mem, + GstNvEncResource ** resource) +{ + GstNvEncResource *res; + GstD3D11Memory *dmem; + NV_ENC_REGISTER_RESOURCE new_resource; + NV_ENC_MAP_INPUT_RESOURCE mapped_resource; + NVENCSTATUS status; + D3D11_TEXTURE2D_DESC desc; + GstMapInfo info; + + if (!gst_is_d3d11_memory (mem)) { + GST_ERROR_ID (id_.c_str (), "Not a D3D11 memory"); + return NV_ENC_ERR_INVALID_CALL; + } + + dmem = GST_D3D11_MEMORY_CAST (mem); + + res = (GstNvEncResource *) gst_d3d11_memory_get_token_data (dmem, + user_token_); + if (res) { + auto iter = resource_queue_.find (res); + /* This resource can be released already */ + if (iter != resource_queue_.end ()) { + GST_LOG_ID (id_.c_str (), "Memory is holding registered resource"); + *resource = gst_nv_enc_resource_ref (res); + return NV_ENC_SUCCESS; + } + } + + if (!gst_memory_map (mem, &info, GST_MAP_READ_NVENC)) { + GST_ERROR_ID (id_.c_str (), "Couldn't map D3D11 memory"); + return NV_ENC_ERR_MAP_FAILED; + } + + gst_d3d11_memory_get_texture_desc (dmem, &desc); + + memset (&new_resource, 0, sizeof (NV_ENC_REGISTER_RESOURCE)); + memset (&mapped_resource, 0, sizeof (NV_ENC_MAP_INPUT_RESOURCE)); + + new_resource.version = gst_nvenc_get_register_resource_version (); + new_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX; + new_resource.width = desc.Width; + new_resource.height = desc.Height; + new_resource.pitch = 0; + new_resource.resourceToRegister = info.data; + new_resource.subResourceIndex = gst_d3d11_memory_get_subresource_index (dmem); + new_resource.bufferFormat = buffer_format_; + + status = NvEncRegisterResource (session_, &new_resource); + gst_memory_unmap (mem, &info); + if (!NVENC_IS_SUCCESS (status, this)) + return status; + + mapped_resource.version = gst_nvenc_get_map_input_resource_version (); + mapped_resource.registeredResource = new_resource.registeredResource; + status = NvEncMapInputResource (session_, &mapped_resource); + if (!NVENC_IS_SUCCESS (status, this)) { + NvEncUnregisterResource (session_, new_resource.registeredResource); + return status; + } + + res = gst_nv_enc_resource_new (id_, resource_seq_.fetch_add (1)); + /* weak ref */ + res->object = shared_from_this (); + + res->resource = new_resource; + res->mapped_resource = mapped_resource; + + gst_d3d11_memory_set_token_data (dmem, user_token_, + gst_nv_enc_resource_ref (res), + (GDestroyNotify) gst_nv_enc_resource_unref); + resource_queue_.insert (res); + + *resource = res; + + return NV_ENC_SUCCESS; +} +#endif + +NVENCSTATUS +GstNvEncObject::AcquireResource (GstMemory * mem, GstNvEncResource ** resource) +{ + NVENCSTATUS status; + std::lock_guard lk (resource_lock_); + +#ifdef G_OS_WIN32 + if (device_type_ == NV_ENC_DEVICE_TYPE_DIRECTX) { + status = acquireResourceD3D11 (mem, resource); + } else +#endif + { + status = acquireResourceCuda (mem, resource); + } + + if (status == NV_ENC_SUCCESS) { + GST_TRACE_ID (id_.c_str (), "Returning resource %u, " + "resource queue size %u (active %u)", + (*resource)->seq_num, (guint) resource_queue_.size (), + (guint) active_resource_queue_.size ()); + } + + return status; +} + +GstFlowReturn +GstNvEncObject::AcquireTask (GstNvEncTask ** task, bool force) +{ + GstNvEncTask *new_task = nullptr; + + std::unique_lock lk (lock_); + + do { + if (!force && flushing_) { + GST_DEBUG_ID (id_.c_str (), "We are flushing"); + return GST_FLOW_FLUSHING; + } + + if (!empty_task_queue_.empty ()) { + new_task = empty_task_queue_.front (); + empty_task_queue_.pop (); + break; + } + + GST_LOG_ID (id_.c_str (), "No available task, waiting for release"); + cond_.wait (lk); + } while (true); + + g_assert (!new_task->object); + + new_task->object = shared_from_this (); + + *task = new_task; + + GST_TRACE_ID (id_.c_str (), "Acquired task %u", new_task->seq_num); + + runResourceGC (); + + return GST_FLOW_OK; +} + +void +GstNvEncObject::PushEmptyTask (GstNvEncTask * task) +{ + std::lock_guard lk (lock_); + + empty_task_queue_.push (task); + cond_.notify_all (); +} + +void +GstNvEncObject::PushEmptyBuffer (GstNvEncBuffer * buffer) +{ + std::lock_guard lk (lock_); + + buffer_queue_.push (buffer); + cond_.notify_all (); +} + +void +GstNvEncObject::releaseResourceUnlocked (GstNvEncResource * resource) +{ + NvEncUnmapInputResource (session_, resource->mapped_resource.mappedResource); + NvEncUnregisterResource (session_, resource->resource.registeredResource); + + resource->mapped_resource.mappedResource = nullptr; + resource->resource.registeredResource = nullptr; +} + +void +GstNvEncObject::ReleaseResource (GstNvEncResource * resource) +{ + std::lock_guard lk (resource_lock_); + + active_resource_queue_.erase (resource); + + auto it = resource_queue_.find (resource); + if (it != resource_queue_.end ()) { + DeviceLock (); + releaseResourceUnlocked (resource); + DeviceUnlock (); + resource_queue_.erase (it); + } +} + +void +GstNvEncObject::DeactivateResource (GstNvEncResource * resource) +{ + std::lock_guard lk (resource_lock_); + + GST_TRACE_ID (resource->id.c_str (), "Deactivating resource %u", + resource->seq_num); + + active_resource_queue_.erase (resource); +} + +/* *INDENT-ON* */ + +NVENCSTATUS +gst_nv_enc_buffer_lock (GstNvEncBuffer * buffer, + gpointer * data, guint32 * pitch) +{ + std::shared_ptr < GstNvEncObject > object = buffer->object; + NVENCSTATUS status; + + g_assert (object); + + GST_TRACE_ID (buffer->id.c_str (), "Locking buffer %u", buffer->seq_num); + + if (!buffer->locked) { + buffer->buffer_lock.inputBuffer = buffer->buffer.inputBuffer; + status = NvEncLockInputBuffer (object->GetHandle (), &buffer->buffer_lock); + if (!NVENC_IS_SUCCESS (status, object.get ())) + return status; + + buffer->locked = true; + } + + *data = buffer->buffer_lock.bufferDataPtr; + *pitch = buffer->buffer_lock.pitch; + + return NV_ENC_SUCCESS; +} + +void +gst_nv_enc_buffer_unlock (GstNvEncBuffer * buffer) +{ + std::shared_ptr < GstNvEncObject > object = buffer->object; + + if (!buffer->locked) { + GST_DEBUG_ID (buffer->id.c_str (), + "Buffer %u was not locked", buffer->seq_num); + return; + } + + g_assert (object); + + NvEncUnlockInputBuffer (object->GetHandle (), buffer->buffer.inputBuffer); + buffer->locked = false; +} + +static gboolean +gst_nv_enc_buffer_dispose (GstNvEncBuffer * buffer) +{ + std::shared_ptr < GstNvEncObject > object = buffer->object; + + GST_TRACE_ID (buffer->id.c_str (), "Disposing buffer %u", buffer->seq_num); + + if (!object) + return TRUE; + + gst_nv_enc_buffer_unlock (buffer); + buffer->object = nullptr; + + GST_TRACE_ID (buffer->id.c_str (), + "Back to buffer queue %u", buffer->seq_num); + + /* Back to task queue */ + gst_nv_enc_buffer_ref (buffer); + object->PushEmptyBuffer (buffer); + + return FALSE; +} + +static void +gst_nv_enc_buffer_free (GstNvEncBuffer * buffer) +{ + GST_TRACE_ID (buffer->id.c_str (), "Freeing buffer %u", buffer->seq_num); + + delete buffer; +} + +static GstNvEncBuffer * +gst_nv_enc_buffer_new (const std::string & id, guint seq_num) +{ + GstNvEncBuffer *buffer = new GstNvEncBuffer (id, seq_num); + + gst_mini_object_init (buffer, 0, GST_TYPE_NV_ENC_BUFFER, nullptr, + (GstMiniObjectDisposeFunction) gst_nv_enc_buffer_dispose, + (GstMiniObjectFreeFunction) gst_nv_enc_buffer_free); + + return buffer; +} + +static gboolean +gst_nv_enc_resource_dispose (GstNvEncResource * resource) +{ + std::shared_ptr < GstNvEncObject > object; + + GST_TRACE_ID (resource->id.c_str (), + "Disposing resource %u", resource->seq_num); + + object = resource->object.lock (); + + if (!object) + return TRUE; + + object->ReleaseResource (resource); + + return TRUE; +} + +static void +gst_nv_enc_resource_free (GstNvEncResource * resource) +{ + GST_TRACE_ID (resource->id.c_str (), + "Freeing resource %u", resource->seq_num); + + delete resource; +} + +static GstNvEncResource * +gst_nv_enc_resource_new (const std::string & id, guint seq_num) +{ + GstNvEncResource *resource = new GstNvEncResource (id, seq_num); + + gst_mini_object_init (resource, 0, GST_TYPE_NV_ENC_RESOURCE, nullptr, + (GstMiniObjectDisposeFunction) gst_nv_enc_resource_dispose, + (GstMiniObjectFreeFunction) gst_nv_enc_resource_free); + + return resource; +} + +gboolean +gst_nv_enc_task_set_buffer (GstNvEncTask * task, GstNvEncBuffer * buffer) +{ + g_assert (!task->buffer); + g_assert (!task->resource); + + task->buffer = buffer; + + return TRUE; +} + +gboolean +gst_nv_enc_task_set_resource (GstNvEncTask * task, + GstBuffer * buffer, GstNvEncResource * resource) +{ + if (!gst_buffer_map (buffer, &task->info, GST_MAP_READ_NVENC)) { + GST_ERROR_ID (task->id.c_str (), "Couldn't map resource buffer"); + gst_buffer_unref (buffer); + gst_nv_enc_resource_unref (resource); + return FALSE; + } + + task->gst_buffer = buffer; + task->resource = resource; + + return TRUE; +} + +NVENCSTATUS +gst_nv_enc_task_lock_bitstream (GstNvEncTask * task, + NV_ENC_LOCK_BITSTREAM * bitstream) +{ + NVENCSTATUS status; + + if (task->locked) { + GST_ERROR_ID (task->id.c_str (), "Bitstream was locked already"); + return NV_ENC_ERR_INVALID_CALL; + } + + task->bitstream.outputBitstream = task->output_ptr; + status = task->object->LockBitstream (&task->bitstream); + + if (!NVENC_IS_SUCCESS (status, task->object.get ())) + return status; + + task->locked = true; + *bitstream = task->bitstream; + + return NV_ENC_SUCCESS; +} + +void +gst_nv_enc_task_unlock_bitstream (GstNvEncTask * task) +{ + NVENCSTATUS status; + + if (!task->locked) + return; + + status = task->object->UnlockBitstream (task->output_ptr); + + NVENC_IS_SUCCESS (status, task->object.get ()); + task->locked = false; +} + +static gboolean +gst_nv_enc_task_dispose (GstNvEncTask * task) +{ + std::shared_ptr < GstNvEncObject > object; + + GST_TRACE_ID (task->id.c_str (), "Disposing task %u", task->seq_num); + + object = task->object; + + if (task->resource) { + object->DeactivateResource (task->resource); + gst_clear_nv_encoder_resource (&task->resource); + } + + gst_clear_nv_encoder_buffer (&task->buffer); + + if (task->gst_buffer) { + if (task->device_type == NV_ENC_DEVICE_TYPE_CUDA) { + GstMemory *mem = gst_buffer_peek_memory (task->gst_buffer, 0); + if (gst_is_cuda_memory (mem)) + GST_MEMORY_FLAG_UNSET (mem, GST_CUDA_MEMORY_TRANSFER_NEED_SYNC); + } + gst_buffer_unmap (task->gst_buffer, &task->info); + gst_clear_buffer (&task->gst_buffer); + } + + if (!object) + return TRUE; + + task->object = nullptr; + + GST_TRACE_ID (task->id.c_str (), "Back to task queue %u", task->seq_num); + + /* Back to task queue */ + gst_nv_enc_task_ref (task); + object->PushEmptyTask (task); + + return FALSE; +} + +static void +gst_nv_enc_task_free (GstNvEncTask * task) +{ + GST_TRACE_ID (task->id.c_str (), "Freeing task %u", task->seq_num); + + delete task; +} + +static GstNvEncTask * +gst_nv_enc_task_new (const std::string & id, guint seq_num) +{ + GstNvEncTask *task = new GstNvEncTask (id, seq_num); + + gst_mini_object_init (task, 0, GST_TYPE_NV_ENC_TASK, nullptr, + (GstMiniObjectDisposeFunction) gst_nv_enc_task_dispose, + (GstMiniObjectFreeFunction) gst_nv_enc_task_free); + + return task; +} + +const gchar * +nvenc_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/gstnvencobject.h b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencobject.h new file mode 100644 index 0000000000..c3fdfe28a9 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencobject.h @@ -0,0 +1,247 @@ +/* GStreamer + * Copyright (C) 2023 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 +#include +#ifdef G_OS_WIN32 +#include +#endif +#include "nvEncodeAPI.h" +#include "gstnvenc.h" +#include +#include +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_NV_ENC_BUFFER (gst_nv_enc_buffer_get_type ()) +struct GstNvEncBuffer; + +GType gst_nv_enc_buffer_get_type (void); + +NVENCSTATUS gst_nv_enc_buffer_lock (GstNvEncBuffer * buffer, + gpointer * data, + guint32 * pitch); + +void gst_nv_enc_buffer_unlock (GstNvEncBuffer * buffer); + + +static inline GstNvEncBuffer * +gst_nv_enc_buffer_ref (GstNvEncBuffer * buffer) +{ + return (GstNvEncBuffer *) + gst_mini_object_ref (GST_MINI_OBJECT_CAST (buffer)); +} + +static inline void +gst_nv_enc_buffer_unref (GstNvEncBuffer * buffer) +{ + gst_mini_object_unref (GST_MINI_OBJECT_CAST (buffer)); +} + +static inline void +gst_clear_nv_encoder_buffer (GstNvEncBuffer ** buffer) +{ + if (buffer && *buffer) { + gst_nv_enc_buffer_unref (*buffer); + *buffer = NULL; + } +} + +#define GST_TYPE_NV_ENC_RESOURCE (gst_nv_enc_resource_get_type ()) +struct GstNvEncResource; + +GType gst_nv_enc_resource_get_type (void); + +static inline GstNvEncResource * +gst_nv_enc_resource_ref (GstNvEncResource * resource) +{ + return (GstNvEncResource *) + gst_mini_object_ref (GST_MINI_OBJECT_CAST (resource)); +} + +static inline void +gst_nv_enc_resource_unref (GstNvEncResource * resource) +{ + gst_mini_object_unref (GST_MINI_OBJECT_CAST (resource)); +} + +static inline void +gst_clear_nv_encoder_resource (GstNvEncResource ** resource) +{ + if (resource && *resource) { + gst_nv_enc_resource_unref (*resource); + *resource = NULL; + } +} + +#define GST_TYPE_NV_ENC_TASK (gst_nv_enc_task_get_type ()) +struct GstNvEncTask; + +GType gst_nv_enc_task_get_type (void); + +gboolean gst_nv_enc_task_set_buffer (GstNvEncTask * task, + GstNvEncBuffer * buffer); + +gboolean gst_nv_enc_task_set_resource (GstNvEncTask * task, + GstBuffer * buffer, + GstNvEncResource * resource); + +NVENCSTATUS gst_nv_enc_task_lock_bitstream (GstNvEncTask * task, + NV_ENC_LOCK_BITSTREAM * bitstream); + +void gst_nv_enc_task_unlock_bitstream (GstNvEncTask * task); + +static inline GstNvEncTask * +gst_nv_enc_task_ref (GstNvEncTask * task) +{ + return (GstNvEncTask *) + gst_mini_object_ref (GST_MINI_OBJECT_CAST (task)); +} + +static inline void +gst_nv_enc_task_unref (GstNvEncTask * task) +{ + gst_mini_object_unref (GST_MINI_OBJECT_CAST (task)); +} + +const gchar * nvenc_status_to_string (NVENCSTATUS status); + +G_END_DECLS + +class GstNvEncObject : public std::enable_shared_from_this +{ +public: + static bool IsSuccess (NVENCSTATUS status, + GstNvEncObject * self, + const gchar * file, + const gchar * function, + gint line); + + static std::shared_ptr + CreateInstance (GstElement * client, + GstObject * device, + NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS * params); + + ~GstNvEncObject (); + + gpointer GetHandle (); + + guint GetTaskSize (); + + NVENCSTATUS InitSession (NV_ENC_INITIALIZE_PARAMS * params, + GstCudaStream * stream, + const GstVideoInfo * info, + guint pool_size); + + NVENCSTATUS Reconfigure (NV_ENC_RECONFIGURE_PARAMS * params); + + void SetFlushing (bool flushing); + + NVENCSTATUS Encode (GstVideoCodecFrame * codec_frame, + NV_ENC_PIC_STRUCT pic_struct, + GstNvEncTask * task); + + NVENCSTATUS Drain (GstNvEncTask * task); + + GstFlowReturn GetOutput (GstNvEncTask ** task); + + NVENCSTATUS LockBitstream (NV_ENC_LOCK_BITSTREAM * bitstream); + + NVENCSTATUS UnlockBitstream (NV_ENC_OUTPUT_PTR output_ptr); + + NVENCSTATUS AcquireBuffer (GstNvEncBuffer ** buffer); + + NVENCSTATUS AcquireResource (GstMemory * mem, + GstNvEncResource ** resource); + + GstFlowReturn AcquireTask (GstNvEncTask ** task, + bool force); + + void PushEmptyTask (GstNvEncTask * task); + + void PushEmptyBuffer (GstNvEncBuffer * buffer); + + void ReleaseResource (GstNvEncResource * resource); + + void DeactivateResource (GstNvEncResource * resource); + + bool DeviceLock (); + + bool DeviceUnlock (); + +private: + void releaseResourceUnlocked (GstNvEncResource * resource); + + void releaseTaskUnlocked (GstNvEncTask * task); + + NVENCSTATUS acquireResourceCuda (GstMemory * mem, + GstNvEncResource ** resource); + +#ifdef G_OS_WIN32 + NVENCSTATUS acquireResourceD3D11 (GstMemory * mem, + GstNvEncResource ** resource); +#endif + + void runResourceGC (); + +private: + std::string id_; + std::mutex lock_; + std::recursive_mutex resource_lock_; + std::condition_variable cond_; + /* holding unused GstNvEncBuffer object, holding ownership */ + std::queue buffer_queue_; + + /* GstNvEncResource resource is always owned by GstMemory. + * below two data struct will track the resource's life cycle */ + + /* list of all registered GstNvEncResource, without ownership */ + std::set resource_queue_; + + /* list of GstNvEncResource in task_queue */ + std::set active_resource_queue_; + std::queue task_queue_; + std::queue empty_task_queue_; + gint64 user_token_; + GstCudaContext *context_ = nullptr; + GstCudaStream *stream_ = nullptr; +#ifdef G_OS_WIN32 + GstD3D11Device *device_ = nullptr; +#endif + GstVideoInfo info_; + gpointer session_ = nullptr; + bool initialized_ = false; + bool flushing_ = false; + guint task_size_ = 0; + + NV_ENC_DEVICE_TYPE device_type_ = NV_ENC_DEVICE_TYPE_CUDA; + NV_ENC_BUFFER_FORMAT buffer_format_ = NV_ENC_BUFFER_FORMAT_UNDEFINED; + + std::atomic buffer_seq_; + std::atomic resource_seq_; + std::atomic task_seq_; +}; diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.cpp b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.cpp index d6dbd3eb2b..f54ac8ec1c 100644 --- a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.cpp +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.cpp @@ -28,6 +28,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include "gstnvencobject.h" #ifdef G_OS_WIN32 #include @@ -41,60 +47,52 @@ using namespace Microsoft::WRL; /* *INDENT-ON* */ #endif -GST_DEBUG_CATEGORY_STATIC (gst_nv_encoder_debug); +GST_DEBUG_CATEGORY (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 +#define GST_NVENC_STATUS_FORMAT "s (%d)" +#define GST_NVENC_STATUS_ARGS(s) nvenc_status_to_string (s), s struct _GstNvEncoderPrivate { - GstCudaContext *context; - GstCudaStream *stream; + _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; - GstD3D11Fence *fence; + GstD3D11Device *device = nullptr; + GstD3D11Fence *fence = nullptr; #endif + std::shared_ptr < GstNvEncObject > object; + GstNvEncoderDeviceMode subclass_device_mode; GstNvEncoderDeviceMode selected_device_mode; - gint64 dxgi_adapter_luid; - guint cuda_device_id; + gint64 dxgi_adapter_luid = 0; + guint cuda_device_id = 0; NV_ENC_INITIALIZE_PARAMS init_params; NV_ENC_CONFIG config; - gpointer session; - GstVideoCodecState *input_state; + GstVideoCodecState *input_state = nullptr; - GstBufferPool *internal_pool; + GstBufferPool *internal_pool = nullptr; - GstClockTime dts_offset; + GstClockTime dts_offset = 0; - /* Array of GstNvEncoderTask, holding ownership */ - GArray *task_pool; + std::mutex lock; + std::condition_variable cond; - GQueue free_tasks; - GQueue output_tasks; + std::recursive_mutex context_lock; - GMutex lock; - GCond cond; + std::unique_ptr < std::thread > encoding_thread; - GRecMutex context_lock; - - GThread *encoding_thread; - - GstFlowReturn last_flow; + std::atomic < GstFlowReturn > last_flow; }; /** @@ -103,8 +101,7 @@ struct _GstNvEncoderPrivate * Since: 1.22 */ #define gst_nv_encoder_parent_class parent_class -G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GstNvEncoder, gst_nv_encoder, - GST_TYPE_VIDEO_ENCODER); +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_context (GstElement * element, @@ -112,6 +109,8 @@ static void gst_nv_encoder_set_context (GstElement * element, 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, @@ -124,7 +123,6 @@ 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) @@ -140,6 +138,7 @@ gst_nv_encoder_class_init (GstNvEncoderClass * klass) 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 = @@ -162,22 +161,7 @@ gst_nv_encoder_class_init (GstNvEncoderClass * klass) 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); - - g_rec_mutex_init (&priv->context_lock); + self->priv = new GstNvEncoderPrivate (); gst_video_encoder_set_min_pts (GST_VIDEO_ENCODER (self), GST_SECOND * 60 * 60 * 1000); @@ -187,14 +171,8 @@ 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_mutex_clear (&priv->lock); - g_cond_clear (&priv->cond); - - g_rec_mutex_clear (&priv->context_lock); + delete self->priv; G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -205,7 +183,7 @@ gst_nv_encoder_set_context (GstElement * element, GstContext * context) GstNvEncoder *self = GST_NV_ENCODER (element); GstNvEncoderPrivate *priv = self->priv; - g_rec_mutex_lock (&priv->context_lock); + std::unique_lock < std::recursive_mutex > lk (priv->context_lock); switch (priv->selected_device_mode) { #ifdef G_OS_WIN32 @@ -222,7 +200,7 @@ gst_nv_encoder_set_context (GstElement * element, GstContext * context) break; } - g_rec_mutex_unlock (&priv->context_lock); + lk.unlock (); GST_ELEMENT_CLASS (parent_class)->set_context (element, context); } @@ -234,21 +212,17 @@ gst_nv_encoder_reset (GstNvEncoder * self) 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; + if (priv->encoding_thread) { + priv->encoding_thread->join (); + priv->encoding_thread = nullptr; } - g_queue_clear (&priv->free_tasks); - g_queue_clear (&priv->output_tasks); - + priv->object = nullptr; priv->last_flow = GST_FLOW_OK; return TRUE; @@ -298,53 +272,14 @@ gst_nv_encoder_device_unlock (GstNvEncoder * self) return ret; } -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; + GstNvEncTask *task = nullptr; - if (!priv->session || !priv->encoding_thread) + if (!priv->object || !priv->encoding_thread) return TRUE; GST_DEBUG_OBJECT (self, "Drain"); @@ -352,33 +287,14 @@ gst_nv_encoder_drain (GstNvEncoder * self, gboolean locked) if (locked) GST_VIDEO_ENCODER_STREAM_UNLOCK (self); - gst_nv_encoder_get_free_task (self, &task, FALSE); + priv->object->AcquireTask (&task, true); - task->is_eos = TRUE; + status = priv->object->Drain (task); + gst_nv_enc_result (status, self); - pic_params.version = gst_nvenc_get_pic_params_version (); - pic_params.encodePicFlags = NV_ENC_PIC_FLAG_EOS; - pic_params.completionEvent = task->event_handle; + priv->encoding_thread->join (); + priv->encoding_thread = nullptr; - 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) @@ -488,13 +404,31 @@ gst_nv_encoder_stop (GstVideoEncoder * encoder) 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); +} + static gboolean gst_nv_encoder_handle_context_query (GstNvEncoder * self, GstQuery * query) { GstNvEncoderPrivate *priv = self->priv; gboolean ret = FALSE; - g_rec_mutex_lock (&priv->context_lock); + std::unique_lock < std::recursive_mutex > lk (priv->context_lock); switch (priv->selected_device_mode) { #ifdef G_OS_WIN32 @@ -511,7 +445,6 @@ gst_nv_encoder_handle_context_query (GstNvEncoder * self, GstQuery * query) break; } - g_rec_mutex_unlock (&priv->context_lock); return ret; } @@ -550,6 +483,17 @@ gst_nv_encoder_src_query (GstVideoEncoder * encoder, GstQuery * query) 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) { @@ -643,140 +587,6 @@ gst_nv_encoder_propose_allocation (GstVideoEncoder * encoder, GstQuery * query) 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) { @@ -814,84 +624,8 @@ gst_nv_encoder_get_pic_struct (GstNvEncoder * self, GstBuffer * buffer) 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) +gst_nv_encoder_find_output_frame (GstVideoEncoder * self, GstNvEncTask * task) { GList *frames, *iter; GstVideoCodecFrame *ret = NULL; @@ -900,7 +634,7 @@ gst_nv_encoder_find_output_frame (GstVideoEncoder * self, for (iter = frames; iter; iter = g_list_next (iter)) { GstVideoCodecFrame *frame = (GstVideoCodecFrame *) iter->data; - GstNvEncoderTask *other = (GstNvEncoderTask *) + GstNvEncTask *other = (GstNvEncTask *) gst_video_codec_frame_get_user_data (frame); if (!other) @@ -921,69 +655,54 @@ gst_nv_encoder_find_output_frame (GstVideoEncoder * self, return ret; } -static gpointer +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; - GstNvEncoderTask *task = NULL; + std::shared_ptr < GstNvEncObject > object = priv->object; + + GST_INFO_OBJECT (self, "Entering encoding loop"); do { - NV_ENC_LOCK_BITSTREAM bitstream = { 0, }; - NVENCSTATUS status; - GstVideoCodecFrame *frame; GstFlowReturn ret; + GstNvEncTask *task = nullptr; + GstVideoCodecFrame *frame; + NV_ENC_LOCK_BITSTREAM bitstream; + NVENCSTATUS status; - 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; + 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")); - goto error; + priv->last_flow = GST_FLOW_ERROR; + continue; } - 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); + status = gst_nv_enc_task_lock_bitstream (task, &bitstream); if (status != NV_ENC_SUCCESS) { - gst_nv_encoder_device_unlock (self); + 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))); - goto error; + 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) { @@ -999,57 +718,21 @@ gst_nv_encoder_thread_func (GstNvEncoder * self) 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_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 (ret)); + "Finish frame returned %s", gst_flow_get_name (priv->last_flow)); } + } while (true); - GST_NV_ENCODER_LOCK (self); - /* Any pending GPU command associated with this memory must be finished - * at this point */ - if (task->buffer && priv->stream) { - GstMemory *mem; - - mem = gst_buffer_peek_memory (task->buffer, 0); - if (gst_is_cuda_memory (mem)) - GST_MEMORY_FLAG_UNSET (mem, GST_CUDA_MEMORY_TRANSFER_NEED_SYNC); - } - 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; - } + GST_INFO_OBJECT (self, "Exiting thread"); } static guint @@ -1075,20 +758,21 @@ gst_nv_encoder_calculate_task_pool_size (GstNvEncoder * self, } static gboolean -gst_nv_encoder_open_encode_session (GstNvEncoder * self, gpointer * session) +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 (); - NVENCSTATUS status; + 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: @@ -1100,10 +784,11 @@ gst_nv_encoder_open_encode_session (GstNvEncoder * self, gpointer * session) return FALSE; } - 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)); + 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; } @@ -1202,7 +887,6 @@ gst_nv_encoder_init_session (GstNvEncoder * self, GstBuffer * in_buf) guint task_pool_size; gint fps_n, fps_d; GstClockTime frame_duration, min_latency, max_latency; - guint i; gst_nv_encoder_reset (self); @@ -1232,7 +916,7 @@ gst_nv_encoder_init_session (GstNvEncoder * self, GstBuffer * in_buf) g_assert (data.device_mode == GST_NV_ENCODER_DEVICE_CUDA || data.device_mode == GST_NV_ENCODER_DEVICE_D3D11); - g_rec_mutex_lock (&priv->context_lock); + 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; @@ -1263,7 +947,6 @@ gst_nv_encoder_init_session (GstNvEncoder * self, GstBuffer * in_buf) #endif ret = gst_nv_encoder_open (GST_VIDEO_ENCODER (self)); - g_rec_mutex_unlock (&priv->context_lock); if (!ret) { GST_ELEMENT_ERROR (self, STREAM, ENCODE, (NULL), @@ -1285,83 +968,43 @@ gst_nv_encoder_init_session (GstNvEncoder * self, GstBuffer * in_buf) return FALSE; } - if (!gst_nv_encoder_open_encode_session (self, &priv->session)) { + 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->session, &priv->init_params, - &priv->config)) { + 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 = NvEncInitializeEncoder (priv->session, &priv->init_params); - if (status != NV_ENC_SUCCESS) { + 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; } - if (priv->selected_device_mode == GST_NV_ENCODER_DEVICE_CUDA && - gst_nvenc_have_set_io_cuda_streams () && priv->stream) { - CUstream stream = gst_cuda_stream_get_handle (priv->stream); - status = NvEncSetIOCudaStreams (priv->session, - (NV_ENC_CUSTREAM_PTR) & stream, (NV_ENC_CUSTREAM_PTR) & stream); - if (status != NV_ENC_SUCCESS) { - GST_WARNING_OBJECT (self, "NvEncSetIOCudaStreams failed, status: %" - GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); - } - } - - 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)) { + 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 = g_thread_new ("GstNvEncoderThread", - (GThreadFunc) gst_nv_encoder_thread_func, self); + 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; @@ -1380,7 +1023,7 @@ gst_nv_encoder_init_session (GstNvEncoder * self, GstBuffer * in_buf) min_latency = priv->dts_offset + priv->config.rcParams.lookaheadDepth * frame_duration; - max_latency = frame_duration * priv->task_pool->len; + max_latency = frame_duration * task_pool_size; gst_video_encoder_set_latency (GST_VIDEO_ENCODER (self), min_latency, max_latency); @@ -1401,7 +1044,7 @@ gst_nv_encoder_reconfigure_session (GstNvEncoder * self) NV_ENC_RECONFIGURE_PARAMS params = { 0, }; NVENCSTATUS status; - if (!priv->session) { + if (!priv->object) { GST_WARNING_OBJECT (self, "Encoding session was not configured, open session"); gst_nv_encoder_drain (self, TRUE); @@ -1413,10 +1056,8 @@ gst_nv_encoder_reconfigure_session (GstNvEncoder * self) 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)); + 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); @@ -1445,76 +1086,34 @@ gst_nv_encoder_set_format (GstVideoEncoder * encoder, return gst_nv_encoder_init_session (self, nullptr); } -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) + GstBuffer * buffer, GstNvEncTask * task) { + std::shared_ptr < GstNvEncObject > object = self->priv->object; 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; + 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; } - 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)); + status = object->AcquireBuffer (&inbuf); + if (!gst_nv_enc_result (status, self)) { gst_video_frame_unmap (&frame); return GST_FLOW_ERROR; } - dst_data = (guint8 *) task->lk_input_buffer.bufferDataPtr; + 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); @@ -1525,94 +1124,59 @@ gst_nv_encoder_copy_system (GstNvEncoder * self, const GstVideoInfo * info, for (guint j = 0; j < height; j++) { memcpy (dst_data, src_data, width_in_bytes); - dst_data += task->lk_input_buffer.pitch; + dst_data += pitch; src_data += stride; } } - NvEncUnlockInputBuffer (session, task->input_buffer.inputBuffer); + gst_nv_enc_buffer_unlock (inbuf); gst_video_frame_unmap (&frame); + gst_nv_enc_task_set_buffer (task, inbuf); + return GST_FLOW_OK; } static GstFlowReturn gst_nv_encoder_prepare_task_input_cuda (GstNvEncoder * self, - const GstVideoInfo * info, GstBuffer * buffer, gpointer session, - GstNvEncoderTask * task) + const GstVideoInfo * info, 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; 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); + 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, session, task); + return gst_nv_encoder_copy_system (self, info, buffer, 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); + 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; } - cmem = (GstCudaMemory *) gst_buffer_peek_memory (task->buffer, 0); stream = gst_cuda_memory_get_stream (cmem); if (stream != priv->stream) { /* different stream, needs sync */ gst_cuda_memory_sync (cmem); } - 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; - } + gst_nv_enc_task_set_resource (task, gst_buffer_ref (buffer), resource); return GST_FLOW_OK; } @@ -1777,24 +1341,26 @@ gst_nv_encoder_upload_d3d11_frame (GstNvEncoder * self, static GstFlowReturn gst_nv_encoder_prepare_task_input_d3d11 (GstNvEncoder * self, - const GstVideoInfo * info, GstBuffer * buffer, gpointer session, - GstBufferPool * pool, GstNvEncoderTask * task) + const GstVideoInfo * info, GstBuffer * buffer, + GstBufferPool * pool, GstNvEncTask * task) { GstNvEncoderPrivate *priv = self->priv; + std::shared_ptr < GstNvEncObject > object = priv->object; GstMemory *mem; GstD3D11Memory *dmem; - D3D11_TEXTURE2D_DESC desc; NVENCSTATUS status; + GstBuffer *upload_buffer = nullptr; + GstNvEncResource *resource = nullptr; 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); + 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, session, task); + return gst_nv_encoder_copy_system (self, info, buffer, task); } dmem = GST_D3D11_MEMORY_CAST (mem); @@ -1804,80 +1370,33 @@ gst_nv_encoder_prepare_task_input_d3d11 (GstNvEncoder * self, 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); + 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, session, task); + return gst_nv_encoder_copy_system (self, info, buffer, task); } } - if (!task->buffer) - task->buffer = gst_nv_encoder_upload_d3d11_frame (self, info, buffer, pool); + if (!upload_buffer) + upload_buffer = + gst_nv_encoder_upload_d3d11_frame (self, info, buffer, pool); - if (!task->buffer) { + if (!upload_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); + status = object->AcquireResource (mem, &resource); - 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_ERROR_OBJECT (self, "Failed to get resource, status %" GST_NVENC_STATUS_FORMAT, GST_NVENC_STATUS_ARGS (status)); - - gst_buffer_unmap (task->buffer, &task->map_info); - gst_clear_buffer (&task->buffer); + gst_buffer_unref (upload_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; - } + gst_nv_enc_task_set_resource (task, upload_buffer, resource); return GST_FLOW_OK; } @@ -1885,8 +1404,8 @@ gst_nv_encoder_prepare_task_input_d3d11 (GstNvEncoder * self, static GstFlowReturn gst_nv_encoder_prepare_task_input (GstNvEncoder * self, - const GstVideoInfo * info, GstBuffer * buffer, gpointer session, - GstBufferPool * pool, GstNvEncoderTask * task) + const GstVideoInfo * info, GstBuffer * buffer, + GstBufferPool * pool, GstNvEncTask * task) { GstNvEncoderPrivate *priv = self->priv; GstFlowReturn ret = GST_FLOW_ERROR; @@ -1895,12 +1414,11 @@ gst_nv_encoder_prepare_task_input (GstNvEncoder * self, #ifdef G_OS_WIN32 case GST_NV_ENCODER_DEVICE_D3D11: ret = gst_nv_encoder_prepare_task_input_d3d11 (self, info, buffer, - session, pool, task); + pool, task); break; #endif case GST_NV_ENCODER_DEVICE_CUDA: - ret = gst_nv_encoder_prepare_task_input_cuda (self, info, buffer, - session, task); + ret = gst_nv_encoder_prepare_task_input_cuda (self, info, buffer, task); break; default: g_assert_not_reached (); @@ -1918,24 +1436,20 @@ gst_nv_encoder_handle_frame (GstVideoEncoder * encoder, GstNvEncoderPrivate *priv = self->priv; GstNvEncoderClass *klass = GST_NV_ENCODER_GET_CLASS (self); GstFlowReturn ret = GST_FLOW_ERROR; - GstNvEncoderTask *task = NULL; + GstNvEncTask *task = nullptr; GstNvEncoderReconfigure reconfig; GstBuffer *in_buf = frame->input_buffer; + NVENCSTATUS status; - 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)); + 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 ret; + return priv->last_flow; } - if (!priv->session && !gst_nv_encoder_init_session (self, in_buf)) { + 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); @@ -1966,43 +1480,44 @@ gst_nv_encoder_handle_frame (GstVideoEncoder * encoder, /* 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_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, "Last flow was %s", gst_flow_get_name (ret)); + GST_DEBUG_OBJECT (self, "AcquireTask returned %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); + gst_nv_encoder_device_lock (self); ret = gst_nv_encoder_prepare_task_input (self, &priv->input_state->info, - in_buf, priv->session, priv->internal_pool, task); + in_buf, 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_nv_enc_task_unref (task); gst_video_encoder_finish_frame (encoder, frame); return ret; } - ret = gst_nv_encoder_encode_frame (self, frame, task); - if (ret != GST_FLOW_OK) { + 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 ret; + return GST_FLOW_ERROR; } gst_video_codec_frame_unref (frame); @@ -2037,14 +1552,6 @@ gst_nv_encoder_flush (GstVideoEncoder * encoder) 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_device_mode (GstNvEncoder * encoder, GstNvEncoderDeviceMode mode, guint cuda_device_id, gint64 adapter_luid) @@ -2177,48 +1684,6 @@ gst_nv_encoder_rc_mode_to_native (GstNvEncoderRCMode rc_mode) 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"; -} - GstNvEncoderClassData * gst_nv_encoder_class_data_new (void) { @@ -2419,3 +1884,21 @@ gst_nv_encoder_merge_device_caps (const GstNvEncoderDeviceCaps * a, *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; +} diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.h b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.h index bd3871a522..43f89ad0be 100644 --- a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.h +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvencoder.h @@ -162,27 +162,6 @@ typedef struct gint ref_count; } GstNvEncoderClassData; -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; - typedef struct { GstNvEncoderDeviceMode device_mode; @@ -228,12 +207,6 @@ struct _GstNvEncoderClass 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); @@ -258,6 +231,14 @@ void gst_nv_encoder_merge_device_caps (const GstNvEncoderDeviceCaps * a, const GstNvEncoderDeviceCaps * b, GstNvEncoderDeviceCaps * merged); +gboolean _gst_nv_enc_result (NVENCSTATUS status, + GObject * self, + const gchar * file, + const gchar * function, + gint line); + +#define gst_nv_enc_result(status,self) \ + _gst_nv_enc_result (status, (GObject *) self, __FILE__, GST_FUNCTION, __LINE__) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstNvEncoder, gst_object_unref) diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264encoder.cpp b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264encoder.cpp index 00f12baa26..7858fb8085 100644 --- a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264encoder.cpp +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh264encoder.cpp @@ -1201,9 +1201,8 @@ gst_nv_h264_encoder_set_format (GstNvEncoder * encoder, 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)); + if (!gst_nv_enc_result (status, self)) { + GST_ERROR_OBJECT (self, "Failed to get preset config"); g_mutex_unlock (&self->prop_lock); return FALSE; } @@ -1431,9 +1430,8 @@ gst_nv_h264_encoder_set_output_state (GstNvEncoder * encoder, 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)); + if (!gst_nv_enc_result (status, self)) { + GST_ERROR_OBJECT (self, "Failed to get sequence header"); return FALSE; } diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh265encoder.cpp b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh265encoder.cpp index 1da77682f3..f680ec3862 100644 --- a/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh265encoder.cpp +++ b/subprojects/gst-plugins-bad/sys/nvcodec/gstnvh265encoder.cpp @@ -1184,9 +1184,8 @@ gst_nv_h265_encoder_set_format (GstNvEncoder * encoder, 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)); + if (!gst_nv_enc_result (status, self)) { + GST_ERROR_OBJECT (self, "Failed to get preset config"); g_mutex_unlock (&self->prop_lock); return FALSE; } @@ -1378,9 +1377,8 @@ gst_nv_h265_encoder_set_output_state (GstNvEncoder * encoder, 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)); + if (!gst_nv_enc_result (status, self)) { + GST_ERROR_OBJECT (self, "Failed to get sequence header"); return FALSE; } diff --git a/subprojects/gst-plugins-bad/sys/nvcodec/meson.build b/subprojects/gst-plugins-bad/sys/nvcodec/meson.build index 335fdd407f..be1199d5fa 100644 --- a/subprojects/gst-plugins-bad/sys/nvcodec/meson.build +++ b/subprojects/gst-plugins-bad/sys/nvcodec/meson.build @@ -11,6 +11,7 @@ nvcodec_sources = [ 'gstnvdecobject.cpp', 'gstnvdecoder.cpp', 'gstnvenc.c', + 'gstnvencobject.cpp', 'gstnvencoder.cpp', 'gstnvh264dec.cpp', 'gstnvh264enc.c', @@ -82,6 +83,7 @@ gstnvcodec = library('gstnvcodec', cpp_args : gst_plugins_bad_args + extra_args, include_directories : plugin_incdirs, dependencies : [gstbase_dep, gstvideo_dep, gstpbutils_dep, gstgl_dep, gstglproto_dep, gmodule_dep, gstcodecs_dep, gstd3d11_dep, gstcuda_dep], + override_options : ['cpp_std=c++14'], install : true, install_dir : plugins_install_dir, )