/* 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); static void gst_nv_enc_task_clear_sei (NV_ENC_SEI_PAYLOAD * payload) { g_clear_pointer (&payload->payload, g_free); } 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 (); sei_payload = g_array_new (FALSE, FALSE, sizeof (NV_ENC_SEI_PAYLOAD)); g_array_set_clear_func (sei_payload, (GDestroyNotify) gst_nv_enc_task_clear_sei); } ~GstNvEncTask () { if (sei_payload) g_array_unref (sei_payload); } 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; GArray *sei_payload; }; 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"); /* Report error to abort if GST_CUDA_CRITICAL_ERRORS is configured */ gst_cuda_result (CUDA_ERROR_NO_DEVICE); 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; } if (memcmp (¶ms->encodeGUID, &NV_ENC_CODEC_H264_GUID, sizeof (GUID)) == 0) { codec_ = GST_NV_ENC_CODEC_H264; } else { codec_ = GST_NV_ENC_CODEC_H265; } 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: case GST_VIDEO_FORMAT_GBR: 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: case GST_VIDEO_FORMAT_GBR_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; lookahead_ = params->encodeConfig->rcParams.lookaheadDepth; 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 (task->sei_payload->len > 0) { if (codec_ == GST_NV_ENC_CODEC_H264) { params.codecPicParams.h264PicParams.seiPayloadArray = &g_array_index (task->sei_payload, NV_ENC_SEI_PAYLOAD, 0); params.codecPicParams.h264PicParams.seiPayloadArrayCnt = task->sei_payload->len; } else { params.codecPicParams.hevcPicParams.seiPayloadArray = &g_array_index (task->sei_payload, NV_ENC_SEI_PAYLOAD, 0); params.codecPicParams.hevcPicParams.seiPayloadArrayCnt = task->sei_payload->len; } } 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); } /* On Windows and if async encoding is enabled, output thread will wait * for completion event. But on Linux, async encoding is not supported. * So, we should wait for NV_ENC_SUCCESS in case of sync mode * (it would introduce latency though). * Otherwise nvEncLockBitstream() will return error */ if (params.completionEvent) { /* Windows only path */ task_queue_.push (task); cond_.notify_all (); } else { pending_task_queue_.push (task); if (status == NV_ENC_SUCCESS) { bool notify = false; /* XXX: nvEncLockBitstream() will return NV_ENC_ERR_INVALID_PARAM * if lookahead is enabled. See also * https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/494 */ while (pending_task_queue_.size() > lookahead_) { notify = true; task_queue_.push (pending_task_queue_.front ()); pending_task_queue_.pop (); } if (notify) 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); while (!pending_task_queue_.empty ()) { task_queue_.push (pending_task_queue_.front ()); pending_task_queue_.pop (); } 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 (); g_array_set_size (new_task->sei_payload, 0); *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; } GArray * gst_nv_enc_task_get_sei_payload (GstNvEncTask * task) { return task->sei_payload; } 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; g_array_set_size (task->sei_payload, 0); 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"; }