/** * SECTION:element-gstmtdataplugins * * The element tries to infer information from the driver camera to see if they are distracted and/or sleepy. * * * Example launch line * |[ * gst-launch-1.0 -v videotestsrc ! video/x-raw ! mtddriversafety detect-drowsiness=true detect-distraction=true driver-location=right ! appsink * ]| * This pipeline is a bin that first detects the face, then detects the facial landmarks on the face, then it uses some of the landmarks to see if the driver is sleepy and/or distracted. * */ #pragma region includes #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include //#include #include "gstmtdsmartrecord.h" #include "nlohmann/json.hpp" #include #include #include #include // or make#include for C++17 and up namespace fs = std::experimental::filesystem; using namespace std; using json = nlohmann::json; #pragma endregion GST_DEBUG_CATEGORY_STATIC(gst_mtdataplugins_debug_category); #define GST_CAT_DEFAULT gst_mtdataplugins_debug_category #pragma region prototypes and forward declarations static void gst_mtdata_smartrecord_init(GstMtdSmartRecord *self); void gst_mtdata_smartrecord_finalize(GObject *object); void gst_mtdata_smartrecord_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); void gst_mtdata_smartrecord_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec); #pragma endregion #define SMARTRECORD_H264_CAPS "video/x-h264, stream-format={byte-stream,avc}" #define SMARTRECORD_H265_CAPS "video/x-h265, stream-format={byte-stream,hvc1}" #ifdef ADD_AUDIO #define SMARTRECORD_AUDIO_CAPS "audio/x-raw, rate=[1, 2147483647], channels=[1, 2147483647], layout=interleaved" #endif #define DEFAULT_VIDEO_RECORD_CACHE_IN_SEC 30 #define DEFAULT_VIDEO_RECORD_DURATION_IN_SEC 30 #define DEFAULT_VIDEO_RECORD_TRIGGER_TIME_BEFORE_EVENT_SEC 15 #define DEFAULT_VIDEO_RECORD_TRIGGER_TIME_AFTER_EVENT_SEC 5 #define DEFAULT_FILE_PATH "/data/videos/" #define DEFAULT_FILE_PREFIX "smartrecord" #define DEFAULT_LOCK_FILE_PATH "/data/videos/locked/" G_DEFINE_TYPE_WITH_CODE(GstMtdSmartRecord, gst_mtdata_smartrecord, GST_TYPE_BIN, GST_DEBUG_CATEGORY_INIT(gst_mtdataplugins_debug_category, "mtdsmartrecord", 0, "debug category for mtdsmartrecord element")); static GstStaticPadTemplate gst_mtdata_smartrecord_sink_template = GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS(SMARTRECORD_H264_CAPS ";" SMARTRECORD_H265_CAPS)); #ifdef ADD_AUDIO static GstStaticPadTemplate gst_mtdata_smartrecord_audio_sink_template = GST_STATIC_PAD_TEMPLATE("audio_0", GST_PAD_SINK, GST_PAD_SOMETIMES, GST_STATIC_CAPS(SMARTRECORD_AUDIO_CAPS)); #endif enum { PROP_0, PROP_VIDEO_RECORD_TRIGGER, PROP_TIME_BEFORE_EVENT_SEC, PROP_TIME_AFTER_EVENT_SEC, PROP_FILE_PATH, PROP_FILE_PREFIX, PROP_FILE_LOCK_PREFIX, }; static void *RecordCompleteCallback(NvDsSRRecordingInfo *pNvDsInfo, void *pSRData) { GST_INFO("RecordCompleteCallback"); if (pSRData != NULL) { SRData *pSRD = (SRData *)pSRData; if (!fs::is_directory(pSRD->lock_file_path) || !fs::exists(pSRD->lock_file_path)) { // Check if src folder exists fs::create_directory(pSRD->lock_file_path); // create src folder } // move file string from(pNvDsInfo->dirpath); from += "/"; from += pNvDsInfo->filename; string to(pSRD->lock_file_path); to += "/"; to += pNvDsInfo->filename; if (std::rename(from.c_str(), to.c_str()) < 0) { // GST_ERROR("error moving file from %s to %s", from.c_str(), to.c_str()); int t = 0; } } return NULL; } static void gst_mtdata_smartrecord_class_init(GstMtdSmartRecordClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); GstBinClass *base_bin_class = GST_BIN_CLASS(klass); gst_element_class_add_static_pad_template(GST_ELEMENT_CLASS(klass), &gst_mtdata_smartrecord_sink_template); #ifdef ADD_AUDIO gst_element_class_add_static_pad_template(GST_ELEMENT_CLASS(klass), &gst_mtdata_smartrecord_audio_sink_template); #endif gst_element_class_set_static_metadata(GST_ELEMENT_CLASS(klass), "MTData Smart Record element uses the NVIdia smart record element internally, this is a wrapper to make it work within gstdaemon", "Generic", "MTData Smart Record", "MTData "); gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_mtdata_smartrecord_finalize); gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_mtdata_smartrecord_set_property); gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_mtdata_smartrecord_get_property); g_object_class_install_property(gobject_class, PROP_VIDEO_RECORD_TRIGGER, g_param_spec_boolean("video-record-trigger", "video-record-trigger", "trigger event to start writing video by setting this to true. automatically switches back to false after trigger is detected: [false|true] ", false, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property(gobject_class, PROP_TIME_BEFORE_EVENT_SEC, g_param_spec_uint("time-before-event-sec", "time-before-event-sec", "number of seconds to go 'back in time' when an event is triggered: [0-120] ", 0, 120, DEFAULT_VIDEO_RECORD_TRIGGER_TIME_BEFORE_EVENT_SEC, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT))); g_object_class_install_property(gobject_class, PROP_TIME_AFTER_EVENT_SEC, g_param_spec_uint("time-after-event-sec", "time-after-event-sec", "number of seconds to add to the file after the last event: [0-120] ", 0, 120, DEFAULT_VIDEO_RECORD_TRIGGER_TIME_AFTER_EVENT_SEC, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT))); g_object_class_install_property(gobject_class, PROP_FILE_PATH, g_param_spec_string("file-path", "file-path", "video file location path [ /data/videos/ ] ", DEFAULT_FILE_PATH, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property(gobject_class, PROP_FILE_PREFIX, g_param_spec_string("file-prefix", "file-prefix", "file prefix [ manditory parameter ] ", DEFAULT_FILE_PREFIX, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property(gobject_class, PROP_FILE_LOCK_PREFIX, g_param_spec_string("file-lock-prefix", "file-lock-prefix", "video location where the files are moved to for upload [ /data/videos/locked/ ] ", DEFAULT_LOCK_FILE_PATH, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); } void gst_mtdata_smartrecord_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GstMtdSmartRecord *self = GST_MTDSMARTRECORD(object); GST_DEBUG_OBJECT(self, "set_property"); gchar *tmp_value; gboolean tmp_bool; uint tmp_uint; switch (property_id) { case PROP_VIDEO_RECORD_TRIGGER: tmp_bool = g_value_get_boolean(value); if (tmp_bool && tmp_bool != self->sr_data.video_record_trigger) { self->sr_data.video_record_trigger = tmp_bool; if (self->sr_data.video_record_trigger) { GST_INFO("triggering video recording"); self->sr_data.video_record_trigger = false; NvDsSRSessionId sessionId; NvDsSRStart(self->sr_data.m_pContext, &(self->sr_data.sessionId), self->sr_data.capture_time_before_event_sec, // time before the event self->sr_data.capture_time_before_event_sec + self->sr_data.capture_time_after_event_sec, // total time &(self->sr_data)); } } break; case PROP_TIME_BEFORE_EVENT_SEC: tmp_uint = g_value_get_uint(value); self->sr_data.capture_time_before_event_sec = tmp_uint; break; case PROP_TIME_AFTER_EVENT_SEC: tmp_uint = g_value_get_uint(value); self->sr_data.capture_time_after_event_sec = tmp_uint; break; case PROP_FILE_PATH: tmp_value = g_strdup(g_value_get_string(value)); if (tmp_value != NULL) { if (self->sr_data.file_path != NULL) g_free(self->sr_data.file_path); self->sr_data.file_path = g_strdup(tmp_value); g_free(tmp_value); } break; case PROP_FILE_PREFIX: tmp_value = g_strdup(g_value_get_string(value)); if (tmp_value != NULL) { if (self->sr_data.file_prefix != NULL) g_free(self->sr_data.file_prefix); self->sr_data.file_prefix = g_strdup(tmp_value); g_free(tmp_value); } break; case PROP_FILE_LOCK_PREFIX: tmp_value = g_strdup(g_value_get_string(value)); if (tmp_value != NULL) { if (self->sr_data.lock_file_path != NULL) g_free(self->sr_data.lock_file_path); self->sr_data.lock_file_path = g_strdup(tmp_value); g_free(tmp_value); } default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } void gst_mtdata_smartrecord_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GstMtdSmartRecord *self = GST_MTDSMARTRECORD(object); GST_DEBUG_OBJECT(self, "get_property"); switch (property_id) { case PROP_VIDEO_RECORD_TRIGGER: g_value_set_boolean(value, self->sr_data.video_record_trigger); break; case PROP_TIME_BEFORE_EVENT_SEC: g_value_set_uint(value, self->sr_data.capture_time_before_event_sec); break; case PROP_TIME_AFTER_EVENT_SEC: g_value_set_uint(value, self->sr_data.capture_time_after_event_sec); break; case PROP_FILE_PATH: g_value_set_string(value, self->sr_data.file_path); break; case PROP_FILE_PREFIX: g_value_set_string(value, self->sr_data.file_prefix); break; case PROP_FILE_LOCK_PREFIX: g_value_set_string(value, self->sr_data.lock_file_path); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void gst_mtdata_smartrecord_init(GstMtdSmartRecord *self) { GstPad *sinkpad, *sinkgpad; // GstPad *audio_sinkpad, *audio_sinkgpad; // set default parameter values self->sr_data.video_record_trigger = false; self->sr_data.capture_time_before_event_sec = DEFAULT_VIDEO_RECORD_TRIGGER_TIME_BEFORE_EVENT_SEC; self->sr_data.capture_time_after_event_sec = DEFAULT_VIDEO_RECORD_TRIGGER_TIME_AFTER_EVENT_SEC; self->sr_data.file_path = g_strdup(DEFAULT_FILE_PATH); self->sr_data.file_prefix = g_strdup(DEFAULT_FILE_PREFIX); self->sr_data.lock_file_path = g_strdup(DEFAULT_LOCK_FILE_PATH); // create smartrecord and setup environment for it self->sr_data.m_initParams.containerType = NVDSSR_CONTAINER_MP4; // Set single callback listener. Unique clients are identifed using client_data provided on Start session self->sr_data.m_initParams.callback = RecordCompleteCallback; // Set both width and height params to zero = no-transcode self->sr_data.m_initParams.width = 0; self->sr_data.m_initParams.height = 0; // Filename prefix uses bintr name by default self->sr_data.m_initParams.fileNamePrefix = const_cast(self->sr_data.file_prefix); self->sr_data.m_initParams.dirpath = const_cast(self->sr_data.file_path); self->sr_data.m_initParams.defaultDuration = DEFAULT_VIDEO_RECORD_DURATION_IN_SEC; self->sr_data.m_initParams.cacheSize = DEFAULT_VIDEO_RECORD_CACHE_IN_SEC; if (NvDsSRCreate(&(self->sr_data.m_pContext), &(self->sr_data.m_initParams)) != NVDSSR_STATUS_OK) { GST_ERROR("Failed to create Smart Record Context "); return; } if (!self->sr_data.m_pContext->recordbin) { GST_ERROR("Could not create nv smart recorder. Exiting."); return; } gst_bin_add_many(GST_BIN(self), self->sr_data.m_pContext->recordbin, NULL); // sink ghost pad sinkpad = gst_element_get_static_pad(self->sr_data.m_pContext->recordbin, "sink"); g_return_if_fail(sinkpad); self->sr_data.sinkgpad = gst_ghost_pad_new("sink", sinkpad); gst_pad_set_active(self->sr_data.sinkgpad, TRUE); gst_element_add_pad(GST_ELEMENT(&(self->base_mtdataplugins)), self->sr_data.sinkgpad); gst_object_unref(sinkpad); #ifdef ADD_AUDIO // audio sink ghost pad audio_sinkpad = gst_element_get_static_pad(self->sr_data.m_pContext->recordbin, "asink"); g_return_if_fail(audio_sinkpad); self->sr_data.audio_sinkgpad = gst_ghost_pad_new("audio_0", audio_sinkpad); gst_pad_set_active(self->sr_data.audio_sinkgpad, TRUE); gst_element_add_pad(GST_ELEMENT(&(self->base_mtdataplugins)), self->sr_data.audio_sinkgpad); gst_object_unref(audio_sinkpad); #endif GST_INFO("----end of init ----"); } void gst_mtdata_smartrecord_finalize(GObject *object) { GstMtdSmartRecord *self = GST_MTDSMARTRECORD(object); GST_DEBUG_OBJECT(self, "finalize"); // clean up object here if (self->sr_data.file_path != NULL) g_free(self->sr_data.file_path); if (self->sr_data.file_prefix != NULL) g_free(self->sr_data.file_prefix); if (self->sr_data.m_pContext) NvDsSRDestroy(self->sr_data.m_pContext); G_OBJECT_CLASS(gst_mtdata_smartrecord_parent_class)->finalize(object); } gboolean register_mtd_smartrecord_element(GstPlugin *plugin) { GST_DEBUG_CATEGORY_INIT(gst_mtdataplugins_debug_category, "mtdsmartrecord", 0, "MTData Smart Record"); return gst_element_register(plugin, "mtdsmartrecord", GST_RANK_NONE, GST_TYPE_MTDSMARTRECORD); }